##### Coverage Report - org.apache.commons.configuration.XMLPropertiesConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
XMLPropertiesConfiguration |
81% | 39/48
83% | 5/6
| 1.9166666666666667;1,917
XMLPropertiesConfiguration$1 |
100% | 2/2
N/A | N/A
| 1.9166666666666667;1,917
XMLPropertiesConfiguration$XMLPropertiesHandler |
100% | 18/18
100% | 4/4
| 1.9166666666666667;1,917

1 |  |
     /*
2 |  |
      * Licensed to the Apache Software Foundation (ASF) under one or more
3 |  |
      * contributor license agreements.  See the NOTICE file distributed with
4 |  |
      * this work for additional information regarding copyright ownership.
5 |  |
      * The ASF licenses this file to You under the Apache License, Version 2.0
6 |  |
      * (the "License"); you may not use this file except in compliance with
7 |  |
      * the License.  You may obtain a copy of the License at
8 |  |
      *
9 |  |
      *     http://www.apache.org/licenses/LICENSE-2.0
10 |  |
      *
11 |  |
      * Unless required by applicable law or agreed to in writing, software
12 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
13 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  |
      * See the License for the specific language governing permissions and
15 |  |
      * limitations under the License.
16 |  |
      */
17 |  |
18 |  |
     package org.apache.commons.configuration;
19 |  |
20 |  |
     import java.io.File;
21 |  |
     import java.io.PrintWriter;
22 |  |
     import java.io.Reader;
23 |  |
     import java.io.Writer;
24 |  |
     import java.net.URL;
25 |  |
     import java.util.Iterator;
26 |  |
     import java.util.List;
27 |  |
     import javax.xml.parsers.SAXParser;
28 |  |
     import javax.xml.parsers.SAXParserFactory;
29 |  |
30 |  |
     import org.apache.commons.lang.StringEscapeUtils;
31 |  |
     import org.apache.commons.lang.StringUtils;
32 |  |
33 |  |
     import org.xml.sax.Attributes;
34 |  |
     import org.xml.sax.EntityResolver;
35 |  |
     import org.xml.sax.InputSource;
36 |  |
     import org.xml.sax.XMLReader;
37 |  |
     import org.xml.sax.helpers.DefaultHandler;
38 |  |
39 |  |
     /**
40 |  |
      * This configuration implements the XML properties format introduced in Java
41 |  |
      * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
42 |  |
      * An XML properties file looks like this:
43 |  |
      *
44 |  |
      * <pre>
45 |  |
      * &lt;?xml version="1.0"?>
46 |  |
      * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
47 |  |
      * &lt;properties>
48 |  |
      *   &lt;comment>Description of the property list&lt;/comment>
49 |  |
      *   &lt;entry key="key1">value1&lt;/entry>
50 |  |
      *   &lt;entry key="key2">value2&lt;/entry>
51 |  |
      *   &lt;entry key="key3">value3&lt;/entry>
52 |  |
      * &lt;/properties>
53 |  |
      * </pre>
54 |  |
      *
55 |  |
      * The Java 5.0 runtime is not required to use this class. The default encoding
56 |  |
      * for this configuration format is UTF-8. Note that unlike
57 |  |
      * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
58 |  |
      * does not support includes.
59 |  |
      *
60 |  |
      * <em>Note:</em>Configuration objects of this type can be read concurrently
61 |  |
      * by multiple threads. However if one of these threads modifies the object,
62 |  |
      * synchronization has to be performed manually.
63 |  |
      *
64 |  |
      * @author Emmanuel Bourg
65 |  |
      * @author Alistair Young
66 |  |
      * @version $Revision: 548098 $, $Date: 2007-06-17 21:34:03 +0200 (So, 17 Jun 2007) $
67 |  |
      * @since 1.1
68 |  |
      */
69 |  |
     public class XMLPropertiesConfiguration extends PropertiesConfiguration
70 |  |
     {
71 |  |
         /**
72 |  |
          * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
73 |  |
          */
74 |  |
         private static final String DEFAULT_ENCODING = "UTF-8";
75 |  |
76 |  |
         // initialization block to set the encoding before loading the file in the constructors
77 |  |
         {
78 | 12 |
             setEncoding(DEFAULT_ENCODING);
79 |  |
         }
80 |  |
81 |  |
         /**
82 |  |
          * Creates an empty XMLPropertyConfiguration object which can be
83 |  |
          * used to synthesize a new Properties file by adding values and
84 |  |
          * then saving(). An object constructed by this C'tor can not be
85 |  |
          * tickled into loading included files because it cannot supply a
86 |  |
          * base for relative includes.
87 |  |
          */
88 |  |
         public XMLPropertiesConfiguration()
89 |  |
         {
90 | 8 |
             super();
91 | 8 |
         }
92 |  |
93 |  |
         /**
94 |  |
          * Creates and loads the xml properties from the specified file.
95 |  |
          * The specified file can contain "include" properties which then
96 |  |
          * are loaded and merged into the properties.
97 |  |
          *
98 |  |
          * @param fileName The name of the properties file to load.
99 |  |
          * @throws ConfigurationException Error while loading the properties file
100 |  |
          */
101 |  |
         public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
102 |  |
         {
103 | 3 |
             super(fileName);
104 | 3 |
         }
105 |  |
106 |  |
         /**
107 |  |
          * Creates and loads the xml properties from the specified file.
108 |  |
          * The specified file can contain "include" properties which then
109 |  |
          * are loaded and merged into the properties.
110 |  |
          *
111 |  |
          * @param file The properties file to load.
112 |  |
          * @throws ConfigurationException Error while loading the properties file
113 |  |
          */
114 |  |
         public XMLPropertiesConfiguration(File file) throws ConfigurationException
115 |  |
         {
116 | 1 |
             super(file);
117 | 1 |
         }
118 |  |
119 |  |
         /**
120 |  |
          * Creates and loads the xml properties from the specified URL.
121 |  |
          * The specified file can contain "include" properties which then
122 |  |
          * are loaded and merged into the properties.
123 |  |
          *
124 |  |
          * @param url The location of the properties file to load.
125 |  |
          * @throws ConfigurationException Error while loading the properties file
126 |  |
          */
127 |  |
         public XMLPropertiesConfiguration(URL url) throws ConfigurationException
128 |  |
         {
129 | 0 |
             super(url);
130 | 0 |
         }
131 |  |
132 |  |
         public void load(Reader in) throws ConfigurationException
133 |  |
         {
134 | 12 |
             SAXParserFactory factory = SAXParserFactory.newInstance();
135 | 12 |
             factory.setNamespaceAware(false);
136 | 12 |
             factory.setValidating(true);
137 |  |
138 |  |
             try
139 |  |
             {
140 | 12 |
                 SAXParser parser = factory.newSAXParser();
141 |  |
142 | 12 |
                 XMLReader xmlReader = parser.getXMLReader();
143 | 12 |
                 xmlReader.setEntityResolver(new EntityResolver()
144 |  |
                 {
145 | 12 |
                     public InputSource resolveEntity(String publicId, String systemId)
146 |  |
                     {
147 | 12 |
                         return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
148 |  |
                     }
149 |  |
                 });
150 | 12 |
                 xmlReader.setContentHandler(new XMLPropertiesHandler());
151 | 12 |
                 xmlReader.parse(new InputSource(in));
152 |  |
             }
153 | 0 |
             catch (Exception e)
154 |  |
             {
155 | 0 |
                 throw new ConfigurationException("Unable to parse the configuration file", e);
156 | 12 |
             }
157 |  |
158 |  |
             // todo: support included properties ?
159 | 12 |
         }
160 |  |
161 |  |
         public void save(Writer out) throws ConfigurationException
162 |  |
         {
163 | 1 |
             PrintWriter writer = new PrintWriter(out);
164 |  |
165 | 1 |
             String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
166 | 1 |
             writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
167 | 1 |
             writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
168 | 1 |
             writer.println("<properties>");
169 |  |
170 | 1 |
             if (getHeader() != null)
171 |  |
             {
172 | 1 |
                 writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
173 |  |
             }
174 |  |
175 | 1 |
             Iterator keys = getKeys();
176 | 4 |
             while (keys.hasNext())
177 |  |
             {
178 | 3 |
                 String key = (String) keys.next();
179 | 3 |
                 Object value = getProperty(key);
180 |  |
181 | 3 |
                 if (value instanceof List)
182 |  |
                 {
183 | 0 |
                     writeProperty(writer, key, (List) value);
184 |  |
                 }
185 |  |
                 else
186 |  |
                 {
187 | 3 |
                     writeProperty(writer, key, value);
188 |  |
                 }
189 |  |
             }
190 |  |
191 | 1 |
             writer.println("</properties>");
192 | 1 |
             writer.flush();
193 | 1 |
         }
194 |  |
195 |  |
         /**
196 |  |
          * Write a property.
197 |  |
          *
198 |  |
          * @param out the output stream
199 |  |
          * @param key the key of the property
200 |  |
          * @param value the value of the property
201 |  |
          */
202 |  |
         private void writeProperty(PrintWriter out, String key, Object value)
203 |  |
         {
204 |  |
             // escape the key
205 | 3 |
             String k = StringEscapeUtils.escapeXml(key);
206 |  |
207 | 3 |
             if (value != null)
208 |  |
             {
209 |  |
                 // escape the value
210 | 3 |
                 String v = StringEscapeUtils.escapeXml(String.valueOf(value));
211 | 3 |
                 v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
212 |  |
213 | 3 |
                 out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
214 |  |
             }
215 |  |
             else
216 |  |
             {
217 | 0 |
                 out.println("  <entry key=\"" + k + "\"/>");
218 |  |
             }
219 | 3 |
         }
220 |  |
221 |  |
         /**
222 |  |
          * Write a list property.
223 |  |
          *
224 |  |
          * @param out the output stream
225 |  |
          * @param key the key of the property
226 |  |
          * @param values a list with all property values
227 |  |
          */
228 |  |
         private void writeProperty(PrintWriter out, String key, List values)
229 |  |
         {
230 | 0 |
             for (int i = 0; i < values.size(); i++)
231 |  |
             {
232 | 0 |
                 writeProperty(out, key, values.get(i));
233 |  |
             }
234 | 0 |
         }
235 |  |
236 |  |
         /**
237 |  |
          * SAX Handler to parse a XML properties file.
238 |  |
          *
239 |  |
          * @author Alistair Young
240 |  |
          * @since 1.2
241 |  |
          */
242 | 24 |
         private class XMLPropertiesHandler extends DefaultHandler
243 |  |
         {
244 |  |
             /** The key of the current entry being parsed. */
245 |  |
             private String key;
246 |  |
247 |  |
             /** The value of the current entry being parsed. */
248 | 12 |
             private StringBuffer value = new StringBuffer();
249 |  |
250 |  |
             /** Indicates that a comment is being parsed. */
251 |  |
             private boolean inCommentElement;
252 |  |
253 |  |
             /** Indicates that an entry is being parsed. */
254 |  |
             private boolean inEntryElement;
255 |  |
256 |  |
             public void startElement(String uri, String localName, String qName, Attributes attrs)
257 |  |
             {
258 | 60 |
                 if ("comment".equals(qName))
259 |  |
                 {
260 | 12 |
                     inCommentElement = true;
261 |  |
                 }
262 |  |
263 | 60 |
                 if ("entry".equals(qName))
264 |  |
                 {
265 | 36 |
                     key = attrs.getValue("key");
266 | 36 |
                     inEntryElement = true;
267 |  |
                 }
268 | 60 |
             }
269 |  |
270 |  |
             public void endElement(String uri, String localName, String qName)
271 |  |
             {
272 | 60 |
                 if (inCommentElement)
273 |  |
                 {
274 |  |
                     // We've just finished a <comment> element so set the header
275 | 12 |
                     setHeader(value.toString());
276 | 12 |
                     inCommentElement = false;
277 |  |
                 }
278 |  |
279 | 60 |
                 if (inEntryElement)
280 |  |
                 {
281 |  |
                     // We've just finished an <entry> element, so add the key/value pair
282 | 36 |
                     addProperty(key, value.toString());
283 | 36 |
                     inEntryElement = false;
284 |  |
                 }
285 |  |
286 |  |
                 // Clear the element value buffer
287 | 60 |
                 value = new StringBuffer();
288 | 60 |
             }
289 |  |
290 |  |
             public void characters(char[] chars, int start, int length)
291 |  |
             {
292 |  |
                 /**
293 |  |
                  * We're currently processing an element. All character data from now until
294 |  |
                  * the next endElement() call will be the data for this  element.
295 |  |
                  */
296 | 48 |
                 value.append(chars, start, length);
297 | 48 |
             }
298 |  |
         }
299 |  |
     }