001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * Developers:    Sasa Markovic (saxon@jrobin.org)
011     *
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    
026    package org.jrobin.core;
027    
028    import org.w3c.dom.Element;
029    import org.w3c.dom.Node;
030    import org.xml.sax.InputSource;
031    
032    import java.awt.*;
033    import java.io.File;
034    import java.io.IOException;
035    import java.util.*;
036    import java.util.regex.Matcher;
037    import java.util.regex.Pattern;
038    
039    /**
040     * Class used as a base class for various XML template related classes. Class provides
041     * methods for XML source parsing and XML tree traversing. XML source may have unlimited
042     * number of placeholders (variables) in the format <code>${variable_name}</code>.
043     * Methods are provided to specify variable values at runtime.
044     * Note that this class has limited functionality: XML source gets parsed, and variable
045     * values are collected. You have to extend this class to do something more useful.<p>
046     */
047    public abstract class XmlTemplate {
048            private static final String PATTERN_STRING = "\\$\\{(\\w+)\\}";
049            private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING);
050    
051            protected Element root;
052            private HashMap<String, Object> valueMap = new HashMap<String, Object>();
053            private HashSet<Node> validatedNodes = new HashSet<Node>();
054    
055            protected XmlTemplate(InputSource xmlSource) throws IOException, RrdException {
056                    root = Util.Xml.getRootElement(xmlSource);
057            }
058    
059            protected XmlTemplate(String xmlString) throws IOException, RrdException {
060                    root = Util.Xml.getRootElement(xmlString);
061            }
062    
063            protected XmlTemplate(File xmlFile) throws IOException, RrdException {
064                    root = Util.Xml.getRootElement(xmlFile);
065            }
066    
067            /**
068             * Removes all placeholder-value mappings.
069             */
070            public void clearValues() {
071                    valueMap.clear();
072            }
073    
074            /**
075             * Sets value for a single XML template variable. Variable name should be specified
076             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
077             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
078             *
079             * @param name  variable name
080             * @param value value to be set in the XML template
081             */
082            public void setVariable(String name, String value) {
083                    valueMap.put(name, value);
084            }
085    
086            /**
087             * Sets value for a single XML template variable. Variable name should be specified
088             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
089             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
090             *
091             * @param name  variable name
092             * @param value value to be set in the XML template
093             */
094            public void setVariable(String name, int value) {
095                    valueMap.put(name, value);
096            }
097    
098            /**
099             * Sets value for a single XML template variable. Variable name should be specified
100             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
101             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
102             *
103             * @param name  variable name
104             * @param value value to be set in the XML template
105             */
106            public void setVariable(String name, long value) {
107                    valueMap.put(name, value);
108            }
109    
110            /**
111             * Sets value for a single XML template variable. Variable name should be specified
112             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
113             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
114             *
115             * @param name  variable name
116             * @param value value to be set in the XML template
117             */
118            public void setVariable(String name, double value) {
119                    valueMap.put(name, value);
120            }
121    
122            /**
123             * Sets value for a single XML template variable. Variable name should be specified
124             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
125             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
126             *
127             * @param name  variable name
128             * @param value value to be set in the XML template
129             */
130            public void setVariable(String name, Color value) {
131                    String r = byteToHex(value.getRed());
132                    String g = byteToHex(value.getGreen());
133                    String b = byteToHex(value.getBlue());
134                    String a = byteToHex(value.getAlpha());
135                    valueMap.put(name, "#" + r + g + b + a);
136            }
137    
138            private String byteToHex(int i) {
139                    String s = Integer.toHexString(i);
140                    while (s.length() < 2) {
141                            s = "0" + s;
142                    }
143                    return s;
144            }
145    
146            /**
147             * Sets value for a single XML template variable. Variable name should be specified
148             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
149             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
150             *
151             * @param name  variable name
152             * @param value value to be set in the XML template
153             */
154            public void setVariable(String name, Date value) {
155                    setVariable(name, Util.getTimestamp(value));
156            }
157    
158            /**
159             * Sets value for a single XML template variable. Variable name should be specified
160             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
161             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
162             *
163             * @param name  variable name
164             * @param value value to be set in the XML template
165             */
166            public void setVariable(String name, Calendar value) {
167                    setVariable(name, Util.getTimestamp(value));
168            }
169    
170            /**
171             * Sets value for a single XML template variable. Variable name should be specified
172             * without leading '${' and ending '}' placeholder markers. For example, for a placeholder
173             * <code>${start}</code>, specify <code>start</start> for the <code>name</code> parameter.
174             *
175             * @param name  variable name
176             * @param value value to be set in the XML template
177             */
178            public void setVariable(String name, boolean value) {
179                    valueMap.put(name, "" + value);
180            }
181    
182            /**
183             * Searches the XML template to see if there are variables in there that
184             * will need to be set.
185             *
186             * @return True if variables were detected, false if not.
187             */
188            public boolean hasVariables() {
189                    return PATTERN.matcher(root.toString()).find();
190            }
191    
192            /**
193             * Returns the list of variables that should be set in this template.
194             *
195             * @return List of variable names as an array of strings.
196             */
197            public String[] getVariables() {
198                    ArrayList<String> list = new ArrayList<String>();
199                    Matcher m = PATTERN.matcher(root.toString());
200    
201                    while (m.find()) {
202                            String var = m.group(1);
203                            if (!list.contains(var)) {
204                                    list.add(var);
205                            }
206                    }
207    
208                    return list.toArray(new String[list.size()]);
209            }
210    
211            protected static Node[] getChildNodes(Node parentNode, String childName) {
212                    return Util.Xml.getChildNodes(parentNode, childName);
213            }
214    
215            protected static Node[] getChildNodes(Node parentNode) {
216                    return Util.Xml.getChildNodes(parentNode, null);
217            }
218    
219            protected static Node getFirstChildNode(Node parentNode, String childName) throws RrdException {
220                    return Util.Xml.getFirstChildNode(parentNode, childName);
221            }
222    
223            protected boolean hasChildNode(Node parentNode, String childName) {
224                    return Util.Xml.hasChildNode(parentNode, childName);
225            }
226    
227            protected String getChildValue(Node parentNode, String childName) throws RrdException {
228                    return getChildValue(parentNode, childName, true);
229            }
230    
231            protected String getChildValue(Node parentNode, String childName, boolean trim) throws RrdException {
232                    String value = Util.Xml.getChildValue(parentNode, childName, trim);
233                    return resolveMappings(value);
234            }
235    
236            protected String getValue(Node parentNode) {
237                    return getValue(parentNode, true);
238            }
239    
240            protected String getValue(Node parentNode, boolean trim) {
241                    String value = Util.Xml.getValue(parentNode, trim);
242                    return resolveMappings(value);
243            }
244    
245            private String resolveMappings(String templateValue) {
246                    if (templateValue == null) {
247                            return null;
248                    }
249                    Matcher matcher = PATTERN.matcher(templateValue);
250                    StringBuffer result = new StringBuffer();
251                    int lastMatchEnd = 0;
252                    while (matcher.find()) {
253                            String var = matcher.group(1);
254                            if (valueMap.containsKey(var)) {
255                                    // mapping found
256                                    result.append(templateValue.substring(lastMatchEnd, matcher.start()));
257                                    result.append(valueMap.get(var).toString());
258                                    lastMatchEnd = matcher.end();
259                            }
260                            else {
261                                    // no mapping found - this is illegal
262                                    // throw runtime exception
263                                    throw new IllegalArgumentException("No mapping found for template variable ${" + var + "}");
264                            }
265                    }
266                    result.append(templateValue.substring(lastMatchEnd));
267                    return result.toString();
268            }
269    
270            protected int getChildValueAsInt(Node parentNode, String childName) throws RrdException {
271                    String valueStr = getChildValue(parentNode, childName);
272                    return Integer.parseInt(valueStr);
273            }
274    
275            protected int getValueAsInt(Node parentNode) {
276                    String valueStr = getValue(parentNode);
277                    return Integer.parseInt(valueStr);
278            }
279    
280            protected long getChildValueAsLong(Node parentNode, String childName) throws RrdException {
281                    String valueStr = getChildValue(parentNode, childName);
282                    return Long.parseLong(valueStr);
283            }
284    
285            protected long getValueAsLong(Node parentNode) {
286                    String valueStr = getValue(parentNode);
287                    return Long.parseLong(valueStr);
288            }
289    
290            protected double getChildValueAsDouble(Node parentNode, String childName) throws RrdException {
291                    String valueStr = getChildValue(parentNode, childName);
292                    return Util.parseDouble(valueStr);
293            }
294    
295            protected double getValueAsDouble(Node parentNode) {
296                    String valueStr = getValue(parentNode);
297                    return Util.parseDouble(valueStr);
298            }
299    
300            protected boolean getChildValueAsBoolean(Node parentNode, String childName) throws RrdException {
301                    String valueStr = getChildValue(parentNode, childName);
302                    return Util.parseBoolean(valueStr);
303            }
304    
305            protected boolean getValueAsBoolean(Node parentNode) {
306                    String valueStr = getValue(parentNode);
307                    return Util.parseBoolean(valueStr);
308            }
309    
310            protected Paint getValueAsColor(Node parentNode) throws RrdException {
311                    String rgbStr = getValue(parentNode);
312                    return Util.parseColor(rgbStr);
313            }
314    
315            protected boolean isEmptyNode(Node node) {
316                    // comment node or empty text node
317                    return node.getNodeName().equals("#comment") ||
318                                    (node.getNodeName().equals("#text") && node.getNodeValue().trim().length() == 0);
319            }
320    
321            protected void validateTagsOnlyOnce(Node parentNode, String[] allowedChildNames) throws RrdException {
322                    // validate node only once
323                    if (validatedNodes.contains(parentNode)) {
324                            return;
325                    }
326                    Node[] childs = getChildNodes(parentNode);
327                    main:
328                    for (Node child : childs) {
329                            String childName = child.getNodeName();
330                            for (int j = 0; j < allowedChildNames.length; j++) {
331                                    if (allowedChildNames[j].equals(childName)) {
332                                            // only one such tag is allowed
333                                            allowedChildNames[j] = "<--removed-->";
334                                            continue main;
335                                    }
336                                    else if (allowedChildNames[j].equals(childName + "*")) {
337                                            // several tags allowed
338                                            continue main;
339                                    }
340                            }
341                            if (!isEmptyNode(child)) {
342                                    throw new RrdException("Unexpected tag encountered: <" + childName + ">");
343                            }
344                    }
345                    // everything is OK
346                    validatedNodes.add(parentNode);
347            }
348    }