001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.html;
018
019import java.io.PrintStream;
020import java.util.IdentityHashMap;
021import java.util.Map;
022
023import org.conqat.lib.commons.collections.TwoDimHashMap;
024
025/**
026 * This class is used for managing cascading style sheets. It keeps track of all
027 * declaration blocks and selectors used.
028 * 
029 * @author hummelb
030 */
031public abstract class CSSManagerBase {
032
033        /** Default declarations for elements (i.e. without class). */
034        private final TwoDimHashMap<EHTMLElement, ECSSPseudoClass, CSSDeclarationBlock> defaultDeclarations = new TwoDimHashMap<EHTMLElement, ECSSPseudoClass, CSSDeclarationBlock>();
035
036        /** The names the declaration blocks are registered with. */
037        private final Map<CSSDeclarationBlock, String> classNames = new IdentityHashMap<CSSDeclarationBlock, String>();
038
039        /** Counter used for generating unique CSS class names. */
040        private static int classCounter = 0;
041
042        /** Returns whether there is a default declaration for the given element. */
043        public boolean hasDefaultDeclaration(EHTMLElement element) {
044                return hasDefaultDeclaration(element, ECSSPseudoClass.NONE);
045        }
046
047        /** Returns whether there is a default declaration for the given element. */
048        public boolean hasDefaultDeclaration(EHTMLElement element, ECSSPseudoClass pseudoClass) {
049                return defaultDeclarations.containsKey(element, pseudoClass);
050        }
051
052        /** Adds a single selector and its block to this manager. */
053        public final void addDefaultDeclaration(EHTMLElement element, CSSDeclarationBlock block) {
054                addDefaultDeclaration(element, ECSSPseudoClass.NONE, block);
055        }
056
057        /** Adds a single selector and its block to this manager. */
058        public void addDefaultDeclaration(EHTMLElement element, ECSSPseudoClass pseudoClass, CSSDeclarationBlock block) {
059                if (defaultDeclarations.containsKey(element, pseudoClass)) {
060                        throw new IllegalStateException("May not add element " + element + " twice.");
061                }
062                if (!element.allowsAttribute(EHTMLAttribute.STYLE)) {
063                        throw new IllegalArgumentException("The given element " + element.getName() + " does not support styles!");
064                }
065                defaultDeclarations.putValue(element, pseudoClass, block);
066        }
067
068        /**
069         * Returns the name of the CSS class used for this block. If the block is not
070         * yet known, it is registered with this manager.
071         */
072        public String getCSSClassName(CSSDeclarationBlock block) {
073                String name = classNames.get(block);
074                if (name == null) {
075                        name = generateCSSClassName();
076                        classNames.put(block, name);
077                }
078                return name;
079        }
080
081        /**
082         * Generates a suitable name for a CSS class. This may be overridden by
083         * subclasses. However it must be made sure, that the class names returned are
084         * unique and do not overlap with HTML element names.
085         */
086        protected String generateCSSClassName() {
087                return "CSSCLASS" + ++classCounter;
088        }
089
090        /**
091         * Write all selectors with their blocks to the given stream. The format is the
092         * one usually used in CSS files. This merely calls
093         * {@link #writeOutDefaultDeclarations(PrintStream)} and
094         * {@link #writeOutDeclarations(PrintStream)}.
095         */
096        protected void writeOut(PrintStream ps) {
097
098                writeOutDefaultDeclarations(ps);
099                writeOutDeclarations(ps);
100        }
101
102        /**
103         * Write out default declarations for element (i.e. without specific class).
104         */
105        protected void writeOutDefaultDeclarations(PrintStream ps) {
106                for (EHTMLElement element : defaultDeclarations.getFirstKeys()) {
107                        for (ECSSPseudoClass pseudocssClass : defaultDeclarations.getSecondKeys(element)) {
108                                String selector = element.getName() + pseudocssClass.getName();
109                                writeBlock(defaultDeclarations.getValue(element, pseudocssClass), selector, ps);
110                        }
111                }
112        }
113
114        /** Write out declarations. */
115        protected void writeOutDeclarations(PrintStream ps) {
116                for (Map.Entry<CSSDeclarationBlock, String> entry : classNames.entrySet()) {
117                        writeBlock(entry.getKey(), "." + entry.getValue(), ps);
118                }
119        }
120
121        /** Writes a single block/selector pair to the given stream. */
122        private static void writeBlock(CSSDeclarationBlock block, String selector, PrintStream ps) {
123                ps.print(selector);
124                ps.println(" {");
125                block.writeOut(ps, "  ");
126                ps.println("}");
127                ps.println();
128        }
129}