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.stylesheet;
033
034import java.awt.Color;
035import java.io.BufferedInputStream;
036import java.io.File;
037import java.io.FileInputStream;
038import java.io.IOException;
039import java.io.InputStreamReader;
040import java.io.Reader;
041import java.io.StringReader;
042import java.net.URL;
043import java.util.ArrayList;
044import java.util.HashMap;
045
046import org.graphstream.graph.Edge;
047import org.graphstream.graph.Element;
048import org.graphstream.graph.Graph;
049import org.graphstream.graph.Node;
050import org.graphstream.ui.graphicGraph.GraphicSprite;
051import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.StrokeMode;
052import org.graphstream.ui.graphicGraph.stylesheet.parser.StyleSheetParser;
053import org.graphstream.util.parser.ParseException;
054
055/**
056 * Implementation of the style sheets that can be stored in the graphic graph.
057 * 
058 * @author Antoine Dutot
059 */
060public class StyleSheet {
061        // Attributes
062
063        /**
064         * The top-level default rule.
065         */
066        public Rule defaultRule;
067
068        /**
069         * The default, id and class rules for graphs.
070         */
071        public NameSpace graphRules = new NameSpace(Selector.Type.GRAPH);
072
073        /**
074         * The default, id and class rules for nodes.
075         */
076        public NameSpace nodeRules = new NameSpace(Selector.Type.NODE);
077
078        /**
079         * The default, id and class rules for edges.
080         */
081        public NameSpace edgeRules = new NameSpace(Selector.Type.EDGE);
082
083        /**
084         * The default, id and class rules for sprites.
085         */
086        public NameSpace spriteRules = new NameSpace(Selector.Type.SPRITE);
087
088        /**
089         * Set of listeners.
090         */
091        public ArrayList<StyleSheetListener> listeners = new ArrayList<StyleSheetListener>();
092
093        // Constructors
094
095        /**
096         * New style sheet initialised to defaults.
097         */
098        public StyleSheet() {
099                initRules();
100        }
101
102        // Access
103
104        /**
105         * The default rule for graphs.
106         * 
107         * @return A rule.
108         */
109        public Rule getDefaultGraphRule() {
110                return graphRules.defaultRule;
111        }
112
113        /**
114         * The default rule for nodes.
115         * 
116         * @return A rule.
117         */
118        public Rule getDefaultNodeRule() {
119                return nodeRules.defaultRule;
120        }
121
122        /**
123         * The default rule for edges.
124         * 
125         * @return A rule.
126         */
127        public Rule getDefaultEdgeRule() {
128                return edgeRules.defaultRule;
129        }
130
131        /**
132         * The default rule for sprites.
133         * 
134         * @return A rule.
135         */
136        public Rule getDefaultSpriteRule() {
137                return spriteRules.defaultRule;
138        }
139
140        /**
141         * The default style for graphs.
142         * 
143         * @return A style.
144         */
145        public Style getDefaultGraphStyle() {
146                return getDefaultGraphRule().getStyle();
147        }
148
149        /**
150         * The default style for nodes.
151         * 
152         * @return A style.
153         */
154        public Style getDefaultNodeStyle() {
155                return getDefaultNodeRule().getStyle();
156        }
157
158        /**
159         * The default style for edges.
160         * 
161         * @return A style.
162         */
163        public Style getDefaultEdgeStyle() {
164                return getDefaultEdgeRule().getStyle();
165        }
166
167        /**
168         * The default style for sprites.
169         * 
170         * @return A style.
171         */
172        public Style getDefaultSpriteStyle() {
173                return getDefaultSpriteRule().getStyle();
174        }
175
176        /**
177         * All the rules (default, specific and class) that apply to graphs.
178         * 
179         * @return The set of rules for graphs.
180         */
181        public NameSpace getGraphStyleNameSpace() {
182                return graphRules;
183        }
184
185        /**
186         * All the rules (default, specific and class) that apply to nodes.
187         * 
188         * @return The set of rules for nodes.
189         */
190        public NameSpace getNodeStyleNameSpace() {
191                return nodeRules;
192        }
193
194        /**
195         * All the rules (default, specific and class) that apply to edges.
196         * 
197         * @return The set of rules for edges.
198         */
199        public NameSpace getEdgeStyleNameSpace() {
200                return edgeRules;
201        }
202
203        /**
204         * All the rules (default, specific and class) that apply to sprites.
205         * 
206         * @return The set of rules for sprites.
207         */
208        public NameSpace getSpriteStyleNameSpace() {
209                return spriteRules;
210        }
211
212        /**
213         * Get the rules that match a given element.
214         * 
215         * First a rule for the identifier of the element is looked for. It is
216         * looked for in its name space (nodes for Node element, etc.) If it is not
217         * found, the default rule for this kind of element is used. This rule is
218         * pushed at start of the returned array of rules.
219         * 
220         * After a rule for the element is found, then the various classes the
221         * element pertains to are looked at and each class rule found is added in
222         * order in the returned array.
223         * 
224         * @param element
225         *            The element a rules are searched for.
226         * @return A set of rules matching the element, with the main rule at index
227         *         0.
228         */
229        public ArrayList<Rule> getRulesFor(Element element) {
230                ArrayList<Rule> rules = null;
231
232                if (element instanceof Graph) {
233                        rules = graphRules.getRulesFor(element);
234                } else if (element instanceof Node) {
235                        rules = nodeRules.getRulesFor(element);
236                } else if (element instanceof Edge) {
237                        rules = edgeRules.getRulesFor(element);
238                } else if (element instanceof GraphicSprite) {
239                        rules = spriteRules.getRulesFor(element);
240                } else {
241                        rules = new ArrayList<Rule>();
242                        rules.add(defaultRule);
243                }
244
245                return rules;
246        }
247
248        /**
249         * Compute the name of the style group and element will pertain to knowing
250         * its styling rules.
251         * 
252         * @param element
253         *            The element.
254         * @param rules
255         *            The styling rules.
256         * @return The unique identifier of the style group for the element.
257         * @see #getRulesFor(Element)
258         */
259        public String getStyleGroupIdFor(Element element, ArrayList<Rule> rules) {
260                StringBuilder builder = new StringBuilder();
261
262                if (element instanceof Graph) {
263                        builder.append("g");
264                } else if (element instanceof Node) {
265                        builder.append("n");
266                } else if (element instanceof Edge) {
267                        builder.append("e");
268                } else if (element instanceof GraphicSprite) {
269                        builder.append("s");
270                } else {
271                        throw new RuntimeException("What ?");
272                }
273
274                if (rules.get(0).selector.getId() != null) {
275                        builder.append('_');
276                        builder.append(rules.get(0).selector.getId());
277                }
278
279                int n = rules.size();
280
281                if (n > 1) {
282                        builder.append('(');
283                        builder.append(rules.get(1).selector.getClazz());
284                        for (int i = 2; i < n; i++) {
285                                builder.append(',');
286                                builder.append(rules.get(i).selector.getClazz());
287                        }
288                        builder.append(')');
289                }
290
291                return builder.toString();
292        }
293
294        // Commands
295
296        /**
297         * Create the default rules. This method is the place to set defaults for
298         * specific element types. This is here that the edge width is reset to one,
299         * since the default width is larger. The default z index that is different
300         * for every class of element is also set here.
301         */
302        protected void initRules() {
303                defaultRule = new Rule(new Selector(Selector.Type.ANY), null);
304
305                defaultRule.getStyle().setDefaults();
306
307                graphRules.defaultRule = new Rule(new Selector(Selector.Type.GRAPH),
308                                defaultRule);
309                nodeRules.defaultRule = new Rule(new Selector(Selector.Type.NODE),
310                                defaultRule);
311                edgeRules.defaultRule = new Rule(new Selector(Selector.Type.EDGE),
312                                defaultRule);
313                spriteRules.defaultRule = new Rule(new Selector(Selector.Type.SPRITE),
314                                defaultRule);
315
316                graphRules.defaultRule.getStyle().setValue("padding",
317                                new Values(Style.Units.PX, 30));
318                edgeRules.defaultRule.getStyle().setValue("shape",
319                                StyleConstants.Shape.LINE);
320                edgeRules.defaultRule.getStyle().setValue("size",
321                                new Values(Style.Units.PX, 1));
322                edgeRules.defaultRule.getStyle().setValue("z-index", new Integer(1));
323                nodeRules.defaultRule.getStyle().setValue("z-index", new Integer(2));
324                spriteRules.defaultRule.getStyle().setValue("z-index", new Integer(3));
325
326                Colors colors = new Colors();
327                colors.add(Color.WHITE);
328
329                graphRules.defaultRule.getStyle().setValue("fill-color", colors);
330                graphRules.defaultRule.getStyle().setValue("stroke-mode",
331                                StrokeMode.NONE);
332
333                for (StyleSheetListener listener : listeners) {
334                        listener.styleAdded(defaultRule, defaultRule);
335                        listener.styleAdded(graphRules.defaultRule, graphRules.defaultRule);
336                        listener.styleAdded(nodeRules.defaultRule, nodeRules.defaultRule);
337                        listener.styleAdded(edgeRules.defaultRule, edgeRules.defaultRule);
338                        listener.styleAdded(spriteRules.defaultRule,
339                                        spriteRules.defaultRule);
340                }
341
342                // for( StyleSheetListener listener: listeners )
343                // listener.styleAdded( defaultRule, defaultRule );
344                // for( StyleSheetListener listener: listeners )
345                // listener.styleAdded( graphRules.defaultRule, graphRules.defaultRule
346                // );
347                // for( StyleSheetListener listener: listeners )
348                // listener.styleAdded( nodeRules.defaultRule, nodeRules.defaultRule );
349                // for( StyleSheetListener listener: listeners )
350                // listener.styleAdded( edgeRules.defaultRule, edgeRules.defaultRule );
351                // for( StyleSheetListener listener: listeners )
352                // listener.styleAdded( spriteRules.defaultRule, spriteRules.defaultRule
353                // );
354        }
355
356        /**
357         * Add a listener for style events. You never receive events for default
358         * rules and styles. You receive events only for the rules and styles that
359         * are added after this listener is registered.
360         * 
361         * @param listener
362         *            The new listener.
363         */
364        public void addListener(StyleSheetListener listener) {
365                listeners.add(listener);
366        }
367
368        /**
369         * Remove a previously registered listener.
370         * 
371         * @param listener
372         *            The listener to remove.
373         */
374        public void removeListener(StyleSheetListener listener) {
375                int index = listeners.indexOf(listener);
376
377                if (index >= 0)
378                        listeners.remove(index);
379        }
380
381        /**
382         * Clear all specific rules and initialise the default rules. The listeners
383         * are not changed.
384         */
385        public void clear() {
386                graphRules.clear();
387                nodeRules.clear();
388                edgeRules.clear();
389                spriteRules.clear();
390                initRules();
391
392                for (StyleSheetListener listener : listeners)
393                        listener.styleSheetCleared();
394        }
395
396        /**
397         * Parse a style sheet from a file. The style sheet will complete the
398         * previously parsed style sheets.
399         * 
400         * @param fileName
401         *            Name of the file containing the style sheet.
402         * @throws IOException
403         *             For any kind of I/O error or parse error.
404         */
405        public void parseFromFile(String fileName) throws IOException {
406                parse(new InputStreamReader(new BufferedInputStream(
407                                new FileInputStream(fileName))));
408        }
409
410        /**
411         * Parse a style sheet from an URL. The style sheet will complete the
412         * previously parsed style sheets. First, this method will search the URL as
413         * SystemRessource, then as a file and if there is no match, just try to
414         * create an URL object giving the URL as constructor's parameter.
415         * 
416         * @param url
417         *            Name of the file containing the style sheet.
418         * @throws IOException
419         *             For any kind of I/O error or parse error.
420         */
421        public void parseFromURL(String url) throws IOException {
422                URL u = StyleSheet.class.getClassLoader().getResource(url);
423                if (u == null) {
424                        File f = new File(url);
425
426                        if (f.exists())
427                                u = f.toURI().toURL();
428                        else
429                                u = new URL(url);
430                }
431
432                parse(new InputStreamReader(u.openStream()));
433        }
434
435        /**
436         * Parse a style sheet from a string. The style sheet will complete the
437         * previously parsed style sheets.
438         * 
439         * @param styleSheet
440         *            The string containing the whole style sheet.
441         * @throws IOException
442         *             For any kind of I/O error or parse error.
443         */
444        public void parseFromString(String styleSheet) throws IOException {
445                parse(new StringReader(styleSheet));
446        }
447
448        /**
449         * Parse only one style, create a rule with the given selector, and add this
450         * rule.
451         * 
452         * @param select
453         *            The elements for which this style must apply.
454         * @param styleString
455         *            The style string to parse.
456         */
457        public void parseStyleFromString(Selector select, String styleString)
458                        throws IOException {
459                StyleSheetParser parser = new StyleSheetParser(this, new StringReader(
460                                styleString));
461
462                Style style = new Style();
463
464                try {
465                        parser.stylesStart(style);
466                } catch (ParseException e) {
467                        throw new IOException(e.getMessage());
468                }
469
470                Rule rule = new Rule(select);
471
472                rule.setStyle(style);
473                addRule(rule);
474        }
475
476        /**
477         * Load a style sheet from an attribute value, the value can either be the
478         * contents of the whole style sheet, or begin by "url". If it starts with
479         * "url", it must then contain between parenthesis the string of the URL to
480         * load. For example:
481         * 
482         * <pre>
483         *              url('file:///some/path/on/the/file/system')
484         * </pre>
485         * 
486         * Or
487         * 
488         * <pre>
489         *              url('http://some/web/url')
490         * </pre>
491         * 
492         * The loaded style sheet will be merged with the styles already present in
493         * the style sheet.
494         * 
495         * @param styleSheetValue
496         *            The style sheet name of content.
497         * @throws IOException
498         *             If the loading or parsing of the style sheet failed.
499         */
500        public void load(String styleSheetValue) throws IOException {
501                if (styleSheetValue.startsWith("url")) {
502                        // Extract the part between '(' and ')'.
503
504                        int beg = styleSheetValue.indexOf('(');
505                        int end = styleSheetValue.lastIndexOf(')');
506
507                        if (beg >= 0 && end > beg)
508                                styleSheetValue = styleSheetValue.substring(beg + 1, end);
509
510                        styleSheetValue = styleSheetValue.trim();
511
512                        // Remove the quotes (') or (").
513
514                        if (styleSheetValue.startsWith("'")) {
515                                beg = 0;
516                                end = styleSheetValue.lastIndexOf('\'');
517
518                                if (beg >= 0 && end > beg)
519                                        styleSheetValue = styleSheetValue.substring(beg + 1, end);
520                        }
521
522                        styleSheetValue = styleSheetValue.trim();
523
524                        if (styleSheetValue.startsWith("\"")) {
525                                beg = 0;
526                                end = styleSheetValue.lastIndexOf('"');
527
528                                if (beg >= 0 && end > beg)
529                                        styleSheetValue = styleSheetValue.substring(beg + 1, end);
530                        }
531
532                        // That's it.
533
534                        parseFromURL(styleSheetValue);
535                } else // Parse from string, the value is considered to be the style
536                                // sheet contents.
537                {
538                        parseFromString(styleSheetValue);
539                }
540        }
541
542        /**
543         * Parse the style sheet from the given reader.
544         * 
545         * @param reader
546         *            The reader pointing at the style sheet.
547         * @throws IOException
548         *             For any kind of I/O error or parse error.
549         */
550        protected void parse(Reader reader) throws IOException {
551                StyleSheetParser parser = new StyleSheetParser(this, reader);
552
553                try {
554                        parser.start();
555                } catch (ParseException e) {
556                        throw new IOException(e.getMessage());
557                }
558        }
559
560        /**
561         * Add a new rule with its style. If the rule selector is just GRAPH, NODE,
562         * EDGE or SPRITE, the default corresponding rules make a copy (or
563         * augmentation) of its style. Else if an id or class is specified the rules
564         * are added (or changed/augmented if the id or class was already set) and
565         * their parent is set to the default graph, node, edge or sprite rules. If
566         * this is an event rule (or meta-class rule), its sibling rule (the same
567         * rule without the meta-class) is searched and created if not found and the
568         * event rule is added as an alternative to it.
569         * 
570         * @param newRule
571         *            The new rule.
572         */
573        public void addRule(Rule newRule) {
574                Rule oldRule = null;
575
576                switch (newRule.selector.getType()) {
577                case ANY:
578                        throw new RuntimeException(
579                                        "The ANY selector should never be used, it is created automatically.");
580                case GRAPH:
581                        oldRule = graphRules.addRule(newRule);
582                        break;
583                case NODE:
584                        oldRule = nodeRules.addRule(newRule);
585                        break;
586                case EDGE:
587                        oldRule = edgeRules.addRule(newRule);
588                        break;
589                case SPRITE:
590                        oldRule = spriteRules.addRule(newRule);
591                        break;
592                default:
593                        throw new RuntimeException("Ho ho ho ?");
594                }
595
596                for (StyleSheetListener listener : listeners)
597                        listener.styleAdded(oldRule, newRule);
598        }
599
600        @Override
601        public String toString() {
602                StringBuilder builder = new StringBuilder();
603
604                builder.append("StyleSheet : {\n");
605                builder.append("  default styles:\n");
606                builder.append(defaultRule.toString(1));
607                builder.append(graphRules.toString(1));
608                builder.append(nodeRules.toString(1));
609                builder.append(edgeRules.toString(1));
610                builder.append(spriteRules.toString(1));
611
612                return builder.toString();
613        }
614
615        // Nested classes
616
617        /**
618         * A name space is a tuple (default rule, id rule set, class rule set).
619         * 
620         * <p>
621         * The name space defines a default rule for a kind of elements, a set of
622         * rules for this kind of elements with a given identifier, and a set or
623         * rules for this kind of elements with a given class.
624         * </p>
625         */
626        public class NameSpace {
627                // Attribute
628
629                /**
630                 * The kind of elements in this name space.
631                 */
632                public Selector.Type type;
633
634                /**
635                 * The default rule for this kind of elements.
636                 */
637                public Rule defaultRule;
638
639                /**
640                 * The set of rules for this kind of elements with a given identifier.
641                 */
642                public HashMap<String, Rule> byId = new HashMap<String, Rule>();
643
644                /**
645                 * The set of rules for this kind of elements with a given class.
646                 */
647                public HashMap<String, Rule> byClass = new HashMap<String, Rule>();
648
649                // Constructor
650
651                public NameSpace(Selector.Type type) {
652                        this.type = type;
653                }
654
655                // Access
656
657                /**
658                 * The kind of elements this name space applies rules to.
659                 * 
660                 * @return A type of element (node, edge, sprite, graph).
661                 */
662                public Selector.Type getGraphElementType() {
663                        return type;
664                }
665
666                /**
667                 * Number of specific (id) rules.
668                 * 
669                 * @return The number of rules that apply to elements by their
670                 *         identifiers.
671                 */
672                public int getIdRulesCount() {
673                        return byId.size();
674                }
675
676                /**
677                 * Number of specific (class) rules.
678                 * 
679                 * @return The number of rules that apply to elements by their classes.
680                 */
681                public int getClassRulesCount() {
682                        return byClass.size();
683                }
684
685                /**
686                 * Get the rules that match a given element. The rules are returned in a
687                 * given order. The array always contain the "main" rule that matches
688                 * the element. This rule is either a default rule for the kind of
689                 * element given or the rule that matches its identifier if there is
690                 * one. Then class rules the element has can be appended to this array
691                 * in order.
692                 * 
693                 * @return an array of rules that match the element, with the main rule
694                 *         at index 0.
695                 */
696                protected ArrayList<Rule> getRulesFor(Element element) {
697                        Rule rule = byId.get(element.getId());
698                        ArrayList<Rule> rules = new ArrayList<Rule>();
699
700                        if (rule != null)
701                                rules.add(rule);
702                        else
703                                rules.add(defaultRule);
704
705                        getClassRules(element, rules);
706
707                        if (rules.isEmpty())
708                                rules.add(defaultRule);
709
710                        return rules;
711                }
712
713                /**
714                 * Search if the given element has classes attributes and fill the given
715                 * array with the set of rules that match these classes.
716                 * 
717                 * @param element
718                 *            The element for which classes must be found.
719                 * @param rules
720                 *            The rule array to fill.
721                 */
722                protected void getClassRules(Element element, ArrayList<Rule> rules) {
723                        Object o = element.getAttribute("ui.class");
724
725                        if (o != null) {
726                                if (o instanceof Object[]) {
727                                        for (Object s : (Object[]) o) {
728                                                if (s instanceof CharSequence) {
729                                                        Rule rule = byClass.get((CharSequence) s);
730
731                                                        if (rule != null)
732                                                                rules.add(rule);
733                                                }
734                                        }
735                                } else if (o instanceof CharSequence) {
736                                        String classList = ((CharSequence) o).toString().trim();
737                                        String[] classes = classList.split("\\s*,\\s*");
738
739                                        for (String c : classes) {
740                                                Rule rule = byClass.get(c);
741
742                                                if (rule != null)
743                                                        rules.add(rule);
744                                        }
745                                } else {
746                                        throw new RuntimeException(
747                                                        "Oups ! class attribute is of type "
748                                                                        + o.getClass().getName());
749                                }
750                        }
751                }
752
753                // Command
754
755                /**
756                 * Remove all styles.
757                 */
758                protected void clear() {
759                        defaultRule = null;
760                        byId.clear();
761                        byClass.clear();
762                }
763
764                /**
765                 * Add a new rule.
766                 * 
767                 * <p>
768                 * Several cases can occur :
769                 * </p>
770                 * 
771                 * <ul>
772                 * <li>The rule to add has an ID or class and the rule does not yet
773                 * exists and is not an event rule : add it directly.</li>
774                 * <li>If the rule has an ID or class but the rule already exists,
775                 * augment to already existing rule.</li>
776                 * <li>If the rule has no ID or class and is not an event, augment the
777                 * default style.</li>
778                 * <li>If the rule is an event, the corresponding normal rule is
779                 * searched, if it does not exists, it is created then or else, the
780                 * event is added to the found rule.</li>
781                 * </ul>
782                 * 
783                 * @param newRule
784                 *            The rule to add or copy.
785                 * @return It the rule added augments an existing rule, this existing
786                 *         rule is returned, else null is returned.
787                 */
788                protected Rule addRule(Rule newRule) {
789                        Rule oldRule = null;
790
791                        if (newRule.selector.getPseudoClass() != null) {
792                                oldRule = addEventRule(newRule);
793                        } else if (newRule.selector.getId() != null) {
794                                oldRule = byId.get(newRule.selector.getId());
795
796                                if (oldRule != null) {
797                                        oldRule.getStyle().augment(newRule.getStyle());
798                                } else {
799                                        byId.put(newRule.selector.getId(), newRule);
800                                        newRule.getStyle().reparent(defaultRule);
801                                }
802                        } else if (newRule.selector.getClazz() != null) {
803                                oldRule = byClass.get(newRule.selector.getClazz());
804
805                                if (oldRule != null) {
806                                        oldRule.getStyle().augment(newRule.getStyle());
807                                } else {
808                                        byClass.put(newRule.selector.getClazz(), newRule);
809                                        newRule.getStyle().reparent(defaultRule);
810                                }
811                        } else {
812                                oldRule = defaultRule;
813                                defaultRule.getStyle().augment(newRule.getStyle());
814                                newRule = defaultRule;
815                        }
816
817                        // That's it.
818
819                        return oldRule;
820                }
821
822                protected Rule addEventRule(Rule newRule) {
823                        Rule parentRule = null;
824
825                        if (newRule.selector.getId() != null) {
826                                parentRule = byId.get(newRule.selector.getId());
827
828                                if (parentRule == null) {
829                                        parentRule = addRule(new Rule(new Selector(
830                                                        newRule.selector.getType(),
831                                                        newRule.selector.getId(),
832                                                        newRule.selector.getClazz())));
833                                }
834                        } else if (newRule.selector.getClazz() != null) {
835                                parentRule = byClass.get(newRule.selector.getClazz());
836
837                                if (parentRule == null) {
838                                        parentRule = addRule(new Rule(new Selector(
839                                                        newRule.selector.getType(),
840                                                        newRule.selector.getId(),
841                                                        newRule.selector.getClazz())));
842                                }
843                        } else {
844                                parentRule = defaultRule;
845                        }
846
847                        newRule.getStyle().reparent(parentRule);
848                        parentRule.getStyle().addAlternateStyle(
849                                        newRule.selector.getPseudoClass(), newRule);
850
851                        return parentRule;
852                }
853
854                @Override
855                public String toString() {
856                        return toString(-1);
857                }
858
859                public String toString(int level) {
860                        String prefix = "";
861
862                        if (level > 0) {
863                                for (int i = 0; i < level; i++)
864                                        prefix += "    ";
865                        }
866
867                        StringBuilder builder = new StringBuilder();
868
869                        builder.append(String
870                                        .format("%s%s default style :%n", prefix, type));
871                        builder.append(defaultRule.toString(level + 1));
872                        toStringRules(level, builder, byId,
873                                        String.format("%s%s id styles", prefix, type));
874                        toStringRules(level, builder, byClass,
875                                        String.format("%s%s class styles", prefix, type));
876
877                        return builder.toString();
878                }
879
880                protected void toStringRules(int level, StringBuilder builder,
881                                HashMap<String, Rule> rules, String title) {
882                        builder.append(title);
883                        builder.append(String.format(" :%n"));
884
885                        for (Rule rule : rules.values())
886                                builder.append(rule.toString(level + 1));
887                }
888        }
889}