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 static org.conqat.lib.commons.html.ECSSProperty.BORDER_BOTTOM_COLOR;
020import static org.conqat.lib.commons.html.ECSSProperty.BORDER_BOTTOM_STYLE;
021import static org.conqat.lib.commons.html.ECSSProperty.BORDER_BOTTOM_WIDTH;
022import static org.conqat.lib.commons.html.ECSSProperty.BORDER_LEFT_COLOR;
023import static org.conqat.lib.commons.html.ECSSProperty.BORDER_LEFT_STYLE;
024import static org.conqat.lib.commons.html.ECSSProperty.BORDER_LEFT_WIDTH;
025import static org.conqat.lib.commons.html.ECSSProperty.BORDER_RIGHT_COLOR;
026import static org.conqat.lib.commons.html.ECSSProperty.BORDER_RIGHT_STYLE;
027import static org.conqat.lib.commons.html.ECSSProperty.BORDER_RIGHT_WIDTH;
028import static org.conqat.lib.commons.html.ECSSProperty.BORDER_TOP_COLOR;
029import static org.conqat.lib.commons.html.ECSSProperty.BORDER_TOP_STYLE;
030import static org.conqat.lib.commons.html.ECSSProperty.BORDER_TOP_WIDTH;
031import static org.conqat.lib.commons.html.ECSSProperty.MARGIN_BOTTOM;
032import static org.conqat.lib.commons.html.ECSSProperty.MARGIN_LEFT;
033import static org.conqat.lib.commons.html.ECSSProperty.MARGIN_RIGHT;
034import static org.conqat.lib.commons.html.ECSSProperty.MARGIN_TOP;
035import static org.conqat.lib.commons.html.ECSSProperty.PADDING_BOTTOM;
036import static org.conqat.lib.commons.html.ECSSProperty.PADDING_LEFT;
037import static org.conqat.lib.commons.html.ECSSProperty.PADDING_RIGHT;
038import static org.conqat.lib.commons.html.ECSSProperty.PADDING_TOP;
039
040import java.io.PrintStream;
041import java.util.ArrayList;
042import java.util.EnumMap;
043import java.util.List;
044import java.util.Map;
045
046/**
047 * This class describes a set of CSS declarations (property value pairs).
048 * Additionally it allows for simple multiple inheritance, where the properties
049 * of all inherited blocks are merged (including the block itself). The classes
050 * coming later in the inheritance list and the block itself will overwrite any
051 * properties defined multiple times.
052 */
053public class CSSDeclarationBlock {
054
055        /** The properties and the values */
056        private final Map<ECSSProperty, String> properties = new EnumMap<ECSSProperty, String>(ECSSProperty.class);
057
058        /** The list of blocks we inherit from. */
059        private final List<CSSDeclarationBlock> inheritsFrom = new ArrayList<CSSDeclarationBlock>();
060
061        /**
062         * Create new declaration block.
063         * 
064         * @param values
065         *            the property value pairs to add (so the number must be even).
066         */
067        public CSSDeclarationBlock(Object... values) {
068                if (values.length % 2 != 0) {
069                        throw new IllegalArgumentException("Expected even number of arguments");
070                }
071                for (int i = 0; i < values.length; i += 2) {
072                        if (!(values[i] instanceof ECSSProperty)) {
073                                throw new IllegalArgumentException(
074                                                "Expected CSS property as parameter " + i + " instead of " + values[i].getClass());
075                        }
076                        if (!(values[i + 1] instanceof String)) {
077                                throw new IllegalArgumentException("Expected property value (String) as parameter " + (i + 1)
078                                                + " instead of " + values[i + 1].getClass());
079                        }
080
081                        setProperty((ECSSProperty) values[i], (String) values[i + 1]);
082                }
083        }
084
085        /**
086         * Create new declaration block.
087         * 
088         * @param superBlock
089         *            the block to inherit from.
090         * @param values
091         *            the property value pairs to add (so the number must be even).
092         */
093        public CSSDeclarationBlock(CSSDeclarationBlock superBlock, Object... values) {
094                this(values);
095                inheritFrom(superBlock);
096        }
097
098        /**
099         * Adds a property to this block. Is a property with this name exists, it
100         * will be overwritten.
101         * 
102         * @return this
103         */
104        public CSSDeclarationBlock setProperty(ECSSProperty property, String value) {
105                properties.put(property, value);
106                return this;
107        }
108
109        /**
110         * Sets all given properties to the same value.
111         * 
112         * @return this
113         */
114        private CSSDeclarationBlock setProperties(String value, ECSSProperty... properties) {
115                for (ECSSProperty p : properties) {
116                        setProperty(p, value);
117                }
118                return this;
119        }
120
121        /**
122         * Sets the margin to the given value.
123         * 
124         * @return this
125         */
126        public CSSDeclarationBlock setMargin(String value) {
127                return setProperties(value, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP);
128        }
129
130        /**
131         * Sets the padding to the given value.
132         * 
133         * @return this
134         */
135        public CSSDeclarationBlock setPadding(String value) {
136                return setProperties(value, PADDING_BOTTOM, PADDING_LEFT, PADDING_RIGHT, PADDING_TOP);
137        }
138
139        /**
140         * Sets the border to the given values.
141         * 
142         * @return this
143         */
144        public CSSDeclarationBlock setBorder(String width, String style, String color) {
145                setBorderWidth(width);
146                setBorderStyle(style);
147                setBorderColor(color);
148                return this;
149        }
150
151        /**
152         * Sets the border width to the given value.
153         * 
154         * @return this
155         */
156        public CSSDeclarationBlock setBorderWidth(String width) {
157                return setProperties(width, BORDER_BOTTOM_WIDTH, BORDER_LEFT_WIDTH, BORDER_RIGHT_WIDTH, BORDER_TOP_WIDTH);
158        }
159
160        /**
161         * Sets the border style to the given value.
162         * 
163         * @return this
164         */
165        public CSSDeclarationBlock setBorderStyle(String style) {
166                return setProperties(style, BORDER_BOTTOM_STYLE, BORDER_LEFT_STYLE, BORDER_RIGHT_STYLE, BORDER_TOP_STYLE);
167        }
168
169        /**
170         * Sets the border color to the given value.
171         * 
172         * @return this
173         */
174        public CSSDeclarationBlock setBorderColor(String color) {
175                return setProperties(color, BORDER_BOTTOM_COLOR, BORDER_LEFT_COLOR, BORDER_RIGHT_COLOR, BORDER_TOP_COLOR);
176        }
177
178        /**
179         * Removes the property from this block (whether it exists or not).
180         * 
181         * @return this
182         */
183        public CSSDeclarationBlock removeProperty(ECSSProperty property) {
184                properties.remove(property);
185                return this;
186        }
187
188        /**
189         * Returns the value of the property (or null if it is not defined for this
190         * block).
191         */
192        public String getProperty(ECSSProperty property) {
193                return properties.get(property);
194        }
195
196        /**
197         * Adds another block to inherit from.
198         * 
199         * @return this
200         */
201        public CSSDeclarationBlock inheritFrom(CSSDeclarationBlock css) {
202                inheritsFrom.add(css);
203                return this;
204        }
205
206        /**
207         * Adds all properties (including those inherited) to the given map. Calling
208         * this with an empty map will result in a map containing the actual
209         * properties of this block.
210         */
211        private void fillFullPropertyMap(Map<ECSSProperty, String> map) {
212                for (CSSDeclarationBlock block : inheritsFrom) {
213                        block.fillFullPropertyMap(map);
214                }
215                map.putAll(properties);
216        }
217
218        /**
219         * Writes the full (including inherited) properties into the given stream
220         * using the format for CSS files, i.e. one property in each line followed
221         * by a colon, the value, and a semicolon.
222         */
223        public void writeOut(PrintStream ps, String indent) {
224                Map<ECSSProperty, String> result = new EnumMap<ECSSProperty, String>(ECSSProperty.class);
225                fillFullPropertyMap(result);
226
227                for (ECSSProperty property : result.keySet()) {
228                        ps.println(indent + property.getName() + ": " + result.get(property) + ";");
229                }
230        }
231
232        /**
233         * Returns the full (including inherited) properties as a single line string
234         * using the format suitable for inline styles as used in HTML.
235         */
236        public String toInlineStyle() {
237                StringBuilder sb = new StringBuilder();
238                Map<ECSSProperty, String> result = new EnumMap<ECSSProperty, String>(ECSSProperty.class);
239                fillFullPropertyMap(result);
240
241                for (ECSSProperty property : result.keySet()) {
242                        sb.append(property.getName() + ": " + result.get(property) + "; ");
243                }
244                return sb.toString();
245        }
246}