%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.commons.configuration.PropertiesConfiguration |
|
|
1 | /* |
|
2 | * Copyright 2001-2005 The Apache Software Foundation. |
|
3 | * |
|
4 | * Licensed under the Apache License, Version 2.0 (the "License") |
|
5 | * you may not use this file except in compliance with the License. |
|
6 | * You may obtain a copy of the License at |
|
7 | * |
|
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 | * |
|
10 | * Unless required by applicable law or agreed to in writing, software |
|
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 | * See the License for the specific language governing permissions and |
|
14 | * limitations under the License. |
|
15 | */ |
|
16 | ||
17 | package org.apache.commons.configuration; |
|
18 | ||
19 | import java.io.File; |
|
20 | import java.io.FilterWriter; |
|
21 | import java.io.IOException; |
|
22 | import java.io.LineNumberReader; |
|
23 | import java.io.Reader; |
|
24 | import java.io.Writer; |
|
25 | import java.io.StringReader; |
|
26 | import java.io.BufferedReader; |
|
27 | import java.net.URL; |
|
28 | import java.util.Date; |
|
29 | import java.util.Iterator; |
|
30 | import java.util.List; |
|
31 | ||
32 | import org.apache.commons.lang.StringEscapeUtils; |
|
33 | import org.apache.commons.lang.StringUtils; |
|
34 | ||
35 | /** |
|
36 | * This is the "classic" Properties loader which loads the values from |
|
37 | * a single or multiple files (which can be chained with "include =". |
|
38 | * All given path references are either absolute or relative to the |
|
39 | * file name supplied in the constructor. |
|
40 | * <p> |
|
41 | * In this class, empty PropertyConfigurations can be built, properties |
|
42 | * added and later saved. include statements are (obviously) not supported |
|
43 | * if you don't construct a PropertyConfiguration from a file. |
|
44 | * |
|
45 | * <p>The properties file syntax is explained here: |
|
46 | * |
|
47 | * <ul> |
|
48 | * <li> |
|
49 | * Each property has the syntax <code>key = value</code> |
|
50 | * </li> |
|
51 | * <li> |
|
52 | * The <i>key</i> may use any character but the equal sign '='. |
|
53 | * </li> |
|
54 | * <li> |
|
55 | * <i>value</i> may be separated on different lines if a backslash |
|
56 | * is placed at the end of the line that continues below. |
|
57 | * </li> |
|
58 | * <li> |
|
59 | * If <i>value</i> is a list of strings, each token is separated |
|
60 | * by a comma ',' by default. |
|
61 | * </li> |
|
62 | * <li> |
|
63 | * Commas in each token are escaped placing a backslash right before |
|
64 | * the comma. |
|
65 | * </li> |
|
66 | * <li> |
|
67 | * If a <i>key</i> is used more than once, the values are appended |
|
68 | * like if they were on the same line separated with commas. |
|
69 | * </li> |
|
70 | * <li> |
|
71 | * Blank lines and lines starting with character '#' are skipped. |
|
72 | * </li> |
|
73 | * <li> |
|
74 | * If a property is named "include" (or whatever is defined by |
|
75 | * setInclude() and getInclude() and the value of that property is |
|
76 | * the full path to a file on disk, that file will be included into |
|
77 | * the configuration. You can also pull in files relative to the parent |
|
78 | * configuration file. So if you have something like the following: |
|
79 | * |
|
80 | * include = additional.properties |
|
81 | * |
|
82 | * Then "additional.properties" is expected to be in the same |
|
83 | * directory as the parent configuration file. |
|
84 | * |
|
85 | * The properties in the included file are added to the parent configuration, |
|
86 | * they do not replace existing properties with the same key. |
|
87 | * |
|
88 | * </li> |
|
89 | * </ul> |
|
90 | * |
|
91 | * <p>Here is an example of a valid extended properties file: |
|
92 | * |
|
93 | * <p><pre> |
|
94 | * # lines starting with # are comments |
|
95 | * |
|
96 | * # This is the simplest property |
|
97 | * key = value |
|
98 | * |
|
99 | * # A long property may be separated on multiple lines |
|
100 | * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ |
|
101 | * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
|
102 | * |
|
103 | * # This is a property with many tokens |
|
104 | * tokens_on_a_line = first token, second token |
|
105 | * |
|
106 | * # This sequence generates exactly the same result |
|
107 | * tokens_on_multiple_lines = first token |
|
108 | * tokens_on_multiple_lines = second token |
|
109 | * |
|
110 | * # commas may be escaped in tokens |
|
111 | * commas.excaped = Hi\, what'up? |
|
112 | * |
|
113 | * # properties can reference other properties |
|
114 | * base.prop = /base |
|
115 | * first.prop = ${base.prop}/first |
|
116 | * second.prop = ${first.prop}/second |
|
117 | * </pre> |
|
118 | * |
|
119 | * @author <a href="mailto:e.bourg@cross-systems.com">Emmanuel Bourg</a> |
|
120 | * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a> |
|
121 | * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> |
|
122 | * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a> |
|
123 | * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
|
124 | * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a> |
|
125 | * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a> |
|
126 | * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> |
|
127 | * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a> |
|
128 | * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> |
|
129 | * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a> |
|
130 | * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> |
|
131 | * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> |
|
132 | * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a> |
|
133 | * @version $Id: PropertiesConfiguration.java 156237 2005-03-05 10:26:22Z oheger $ |
|
134 | */ |
|
135 | public class PropertiesConfiguration extends AbstractFileConfiguration |
|
136 | { |
|
137 | /** |
|
138 | * This is the name of the property that can point to other |
|
139 | * properties file for including other properties files. |
|
140 | */ |
|
141 | 322 | static String include = "include"; |
142 | ||
143 | /** Allow file inclusion or not */ |
|
144 | 5286 | private boolean includesAllowed = true; |
145 | ||
146 | /** Comment header of the .properties file */ |
|
147 | private String header; |
|
148 | ||
149 | /** |
|
150 | * Creates an empty PropertyConfiguration object which can be |
|
151 | * used to synthesize a new Properties file by adding values and |
|
152 | * then saving(). An object constructed by this C'tor can not be |
|
153 | * tickled into loading included files because it cannot supply a |
|
154 | * base for relative includes. |
|
155 | */ |
|
156 | public PropertiesConfiguration() |
|
157 | 1086 | { |
158 | 1086 | setIncludesAllowed(false); |
159 | 1086 | } |
160 | ||
161 | /** |
|
162 | * Creates and loads the extended properties from the specified file. |
|
163 | * The specified file can contain "include = " properties which then |
|
164 | * are loaded and merged into the properties. |
|
165 | * |
|
166 | * @param fileName The name of the properties file to load. |
|
167 | * @throws ConfigurationException Error while loading the properties file |
|
168 | */ |
|
169 | public PropertiesConfiguration(String fileName) throws ConfigurationException |
|
170 | { |
|
171 | 4177 | super(fileName); |
172 | 4154 | } |
173 | ||
174 | /** |
|
175 | * Creates and loads the extended properties from the specified file. |
|
176 | * The specified file can contain "include = " properties which then |
|
177 | * are loaded and merged into the properties. |
|
178 | * |
|
179 | * @param file The properties file to load. |
|
180 | * @throws ConfigurationException Error while loading the properties file |
|
181 | */ |
|
182 | public PropertiesConfiguration(File file) throws ConfigurationException |
|
183 | { |
|
184 | 46 | super(file); |
185 | 46 | } |
186 | ||
187 | /** |
|
188 | * Creates and loads the extended properties from the specified URL. |
|
189 | * The specified file can contain "include = " properties which then |
|
190 | * are loaded and merged into the properties. |
|
191 | * |
|
192 | * @param url The location of the properties file to load. |
|
193 | * @throws ConfigurationException Error while loading the properties file |
|
194 | */ |
|
195 | public PropertiesConfiguration(URL url) throws ConfigurationException |
|
196 | { |
|
197 | 0 | super(url); |
198 | 0 | } |
199 | ||
200 | /** |
|
201 | * Gets the property value for including other properties files. |
|
202 | * By default it is "include". |
|
203 | * |
|
204 | * @return A String. |
|
205 | */ |
|
206 | public static String getInclude() |
|
207 | { |
|
208 | 303514 | return PropertiesConfiguration.include; |
209 | } |
|
210 | ||
211 | /** |
|
212 | * Sets the property value for including other properties files. |
|
213 | * By default it is "include". |
|
214 | * |
|
215 | * @param inc A String. |
|
216 | */ |
|
217 | public static void setInclude(String inc) |
|
218 | { |
|
219 | 46 | PropertiesConfiguration.include = inc; |
220 | 46 | } |
221 | ||
222 | /** |
|
223 | * Controls whether additional files can be loaded by the include = <xxx> |
|
224 | * statement or not. Base rule is, that objects created by the empty |
|
225 | * C'tor can not have included files. |
|
226 | * |
|
227 | * @param includesAllowed includesAllowed True if Includes are allowed. |
|
228 | */ |
|
229 | protected void setIncludesAllowed(boolean includesAllowed) |
|
230 | { |
|
231 | 6303 | this.includesAllowed = includesAllowed; |
232 | 6303 | } |
233 | ||
234 | /** |
|
235 | * Reports the status of file inclusion. |
|
236 | * |
|
237 | * @return True if include files are loaded. |
|
238 | */ |
|
239 | public boolean getIncludesAllowed() |
|
240 | { |
|
241 | 3018 | return this.includesAllowed; |
242 | } |
|
243 | ||
244 | /** |
|
245 | * Return the comment header. |
|
246 | * |
|
247 | * @since 1.1 |
|
248 | */ |
|
249 | public String getHeader() |
|
250 | { |
|
251 | 92 | return header; |
252 | } |
|
253 | ||
254 | /** |
|
255 | * Set the comment header. |
|
256 | * |
|
257 | * @since 1.1 |
|
258 | */ |
|
259 | public void setHeader(String header) |
|
260 | { |
|
261 | 92 | this.header = header; |
262 | 92 | } |
263 | ||
264 | /** |
|
265 | * Load the properties from the given reader. |
|
266 | * Note that the <code>clear()</code> method is not called, so |
|
267 | * the properties contained in the loaded file will be added to the |
|
268 | * actual set of properties. |
|
269 | * |
|
270 | * @param in An InputStream. |
|
271 | * |
|
272 | * @throws ConfigurationException |
|
273 | */ |
|
274 | 3078 | public synchronized void load(Reader in) throws ConfigurationException |
275 | { |
|
276 | 4858 | PropertiesReader reader = new PropertiesReader(in); |
277 | ||
278 | try |
|
279 | { |
|
280 | 62001 | while (true) |
281 | { |
|
282 | 159693 | String line = reader.readProperty(); |
283 | ||
284 | 100770 | if (line == null) |
285 | { |
|
286 | 4858 | break; // EOF |
287 | 58923 | } |
288 | 58923 | |
289 | 92834 | int equalSign = line.indexOf('='); |
290 | 151757 | if (equalSign > 0) |
291 | 58923 | { |
292 | 92834 | String key = line.substring(0, equalSign).trim(); |
293 | 92834 | String value = line.substring(equalSign + 1).trim(); |
294 | ||
295 | // Though some software (e.g. autoconf) may produce |
|
296 | // empty values like foo=\n, emulate the behavior of |
|
297 | // java.util.Properties by setting the value to the |
|
298 | 58923 | // empty string. |
299 | ||
300 | 92834 | if (StringUtils.isNotEmpty(getInclude()) |
301 | 1170 | && key.equalsIgnoreCase(getInclude())) |
302 | { |
|
303 | 3000 | if (getIncludesAllowed()) |
304 | 2304 | { |
305 | 1820 | String [] files = StringUtils.split(value, getDelimiter()); |
306 | 4792 | for (int i = 0; i < files.length; i++) |
307 | { |
|
308 | 1820 | load(ConfigurationUtils.locate(getBasePath(), files[i].trim())); |
309 | } |
|
310 | } |
|
311 | } |
|
312 | 57753 | else |
313 | { |
|
314 | 90986 | addProperty(key, unescapeJava(value, getDelimiter())); |
315 | } |
|
316 | } |
|
317 | } |
|
318 | } |
|
319 | 0 | catch (IOException ioe) |
320 | 3078 | { |
321 | 3078 | throw new ConfigurationException("Could not load configuration from input stream.", ioe); |
322 | 4858 | } |
323 | 4858 | } |
324 | ||
325 | /** |
|
326 | * Save the configuration to the specified stream. |
|
327 | * |
|
328 | * @param writer the output stream used to save the configuration |
|
329 | */ |
|
330 | public void save(Writer writer) throws ConfigurationException |
|
331 | { |
|
332 | 63 | try |
333 | { |
|
334 | 161 | PropertiesWriter out = new PropertiesWriter(writer, getDelimiter()); |
335 | ||
336 | 98 | if (header != null) |
337 | { |
|
338 | 0 | BufferedReader reader = new BufferedReader(class="keyword">new StringReader(header)); |
339 | String line; |
|
340 | 0 | while ((line = reader.readLine()) != null) |
341 | { |
|
342 | 0 | out.writeComment(line); |
343 | } |
|
344 | 0 | out.write("\n"); |
345 | 63 | } |
346 | 63 | |
347 | 161 | out.writeComment("written by PropertiesConfiguration"); |
348 | 98 | out.writeComment(new Date().toString()); |
349 | 161 | out.write("\n"); |
350 | 900 | |
351 | 98 | Iterator keys = getKeys(); |
352 | 2237 | while (keys.hasNext()) |
353 | 837 | { |
354 | 1302 | String key = (String) keys.next(); |
355 | 2139 | Object value = getProperty(key); |
356 | ||
357 | 1590 | if (value instanceof List) |
358 | { |
|
359 | 448 | out.writeProperty(key, (List) value); |
360 | } |
|
361 | 549 | else |
362 | { |
|
363 | 854 | out.writeProperty(key, value); |
364 | } |
|
365 | 63 | } |
366 | ||
367 | 98 | out.flush(); |
368 | } |
|
369 | 0 | catch (IOException e) |
370 | 63 | { |
371 | 63 | throw new ConfigurationException(e.getMessage(), e); |
372 | 98 | } |
373 | 98 | } |
374 | ||
375 | /** |
|
376 | * Extend the setBasePath method to turn includes |
|
377 | * on and off based on the existence of a base path. |
|
378 | * |
|
379 | * @param basePath The new basePath to set. |
|
380 | */ |
|
381 | 2016 | public void setBasePath(String basePath) |
382 | 2016 | { |
383 | 5194 | super.setBasePath(basePath); |
384 | 3178 | setIncludesAllowed(StringUtils.isNotEmpty(basePath)); |
385 | 3178 | } |
386 | ||
387 | /** |
|
388 | * This class is used to read properties lines. These lines do |
|
389 | * not terminate with new-line chars but rather when there is no |
|
390 | * backslash sign a the end of the line. This is used to |
|
391 | * concatenate multiple lines for readability. |
|
392 | */ |
|
393 | public static class PropertiesReader extends LineNumberReader |
|
394 | { |
|
395 | /** |
|
396 | * Constructor. |
|
397 | * |
|
398 | * @param reader A Reader. |
|
399 | */ |
|
400 | public PropertiesReader(Reader reader) |
|
401 | { |
|
402 | super(reader); |
|
403 | } |
|
404 | ||
405 | /** |
|
406 | * Read a property. Returns null if Stream is |
|
407 | * at EOF. Concatenates lines ending with "\". |
|
408 | * Skips lines beginning with "#" and empty lines. |
|
409 | * |
|
410 | * @return A string containing a property value or null |
|
411 | * |
|
412 | * @throws IOException |
|
413 | */ |
|
414 | public String readProperty() throws IOException |
|
415 | { |
|
416 | StringBuffer buffer = new StringBuffer(); |
|
417 | ||
418 | while (true) |
|
419 | { |
|
420 | String line = readLine(); |
|
421 | if (line == null) |
|
422 | { |
|
423 | // EOF |
|
424 | return null; |
|
425 | } |
|
426 | ||
427 | line = line.trim(); |
|
428 | ||
429 | // skip comments and empty lines |
|
430 | if (StringUtils.isEmpty(line) || (line.charAt(0) == '#')) |
|
431 | { |
|
432 | continue; |
|
433 | } |
|
434 | ||
435 | if (line.endsWith("\\")) |
|
436 | { |
|
437 | line = line.substring(0, line.length() - 1); |
|
438 | buffer.append(line); |
|
439 | } |
|
440 | else |
|
441 | { |
|
442 | buffer.append(line); |
|
443 | break; |
|
444 | } |
|
445 | } |
|
446 | return buffer.toString(); |
|
447 | } |
|
448 | } // class PropertiesReader |
|
449 | ||
450 | /** |
|
451 | * This class is used to write properties lines. |
|
452 | */ |
|
453 | public static class PropertiesWriter extends FilterWriter |
|
454 | { |
|
455 | private char delimiter; |
|
456 | ||
457 | /** |
|
458 | * Constructor. |
|
459 | * |
|
460 | * @param writer a Writer object providing the underlying stream |
|
461 | */ |
|
462 | public PropertiesWriter(Writer writer, char delimiter) |
|
463 | { |
|
464 | super(writer); |
|
465 | this.delimiter = delimiter; |
|
466 | } |
|
467 | ||
468 | /** |
|
469 | * Write a property. |
|
470 | * |
|
471 | * @param key |
|
472 | * @param value |
|
473 | * @throws IOException |
|
474 | */ |
|
475 | public void writeProperty(String key, Object value) throws IOException |
|
476 | { |
|
477 | write(key); |
|
478 | write(" = "); |
|
479 | if (value != null) |
|
480 | { |
|
481 | String v = StringEscapeUtils.escapeJava(String.valueOf(value)); |
|
482 | v = StringUtils.replace(v, String.valueOf(delimiter), "\\" + delimiter); |
|
483 | write(v); |
|
484 | } |
|
485 | ||
486 | write('\n'); |
|
487 | } |
|
488 | ||
489 | /** |
|
490 | * Write a property. |
|
491 | * |
|
492 | * @param key The key of the property |
|
493 | * @param values The array of values of the property |
|
494 | */ |
|
495 | public void writeProperty(String key, List values) throws IOException |
|
496 | { |
|
497 | for (int i = 0; i < values.size(); i++) |
|
498 | { |
|
499 | writeProperty(key, values.get(i)); |
|
500 | } |
|
501 | } |
|
502 | ||
503 | /** |
|
504 | * Write a comment. |
|
505 | * |
|
506 | * @param comment |
|
507 | * @throws IOException |
|
508 | */ |
|
509 | public void writeComment(String comment) throws IOException |
|
510 | { |
|
511 | write("# " + comment + "\n"); |
|
512 | } |
|
513 | } // class PropertiesWriter |
|
514 | ||
515 | /** |
|
516 | * <p>Unescapes any Java literals found in the <code>String</code> to a |
|
517 | * <code>Writer</code>.</p> This is a slightly modified version of the |
|
518 | * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't |
|
519 | * drop escaped commas (i.e '\,'). |
|
520 | * |
|
521 | * @param str the <code>String</code> to unescape, may be null |
|
522 | * |
|
523 | * @throws IllegalArgumentException if the Writer is <code>null</code> |
|
524 | */ |
|
525 | 57762 | protected static String unescapeJava(String str, char delimiter) |
526 | { |
|
527 | 91000 | if (str == null) |
528 | { |
|
529 | 57762 | return null; |
530 | 57762 | } |
531 | 148762 | int sz = str.length(); |
532 | 148762 | StringBuffer out = new StringBuffer(sz); |
533 | 148762 | StringBuffer unicode = new StringBuffer(4); |
534 | 705376 | boolean hadSlash = false; |
535 | 91000 | boolean inUnicode = false; |
536 | 1525540 | for (int i = 0; i < sz; i++) |
537 | 556614 | { |
538 | 877926 | char ch = str.class="keyword">charAt(i); |
539 | 877926 | if (inUnicode) |
540 | { |
|
541 | 4716 | // if in unicode, then we're reading unicode |
542 | 4716 | // values in somehow |
543 | 7448 | unicode.append(ch); |
544 | 7448 | if (unicode.length() == 4) |
545 | { |
|
546 | // unicode now contains the four hex digits |
|
547 | // which represents our unicode character |
|
548 | 1179 | try |
549 | 1179 | { |
550 | 3041 | int value = Integer.parseInt(unicode.toString(), 16); |
551 | 3041 | out.append((char) value); |
552 | 3041 | unicode.setLength(0); |
553 | 1862 | inUnicode = false; |
554 | 1862 | hadSlash = false; |
555 | } |
|
556 | 0 | catch (NumberFormatException nfe) |
557 | 1179 | { |
558 | 0 | throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe); |
559 | 1862 | } |
560 | } |
|
561 | continue; |
|
562 | 551898 | } |
563 | ||
564 | 870478 | if (hadSlash) |
565 | 7128 | { |
566 | // handle an escaped value |
|
567 | 18384 | hadSlash = false; |
568 | 1188 | |
569 | 11256 | if (ch=='\\'){ |
570 | 7816 | out.append('\\'); |
571 | } |
|
572 | 9380 | else if (ch=='\''){ |
573 | 5940 | out.append('\''); |
574 | 1188 | } |
575 | 9380 | else if (ch=='\"'){ |
576 | 6628 | out.append('"'); |
577 | } |
|
578 | 7504 | else if (ch=='r'){ |
579 | 4752 | out.append('\r'); |
580 | } |
|
581 | 7504 | else if (ch=='f'){ |
582 | 4752 | out.append('\f'); |
583 | 1188 | } |
584 | 7504 | else if (ch=='t'){ |
585 | 5440 | out.append('\t'); |
586 | 1188 | } |
587 | 5628 | else if (ch=='n'){ |
588 | 4252 | out.append('\n'); |
589 | } |
|
590 | 3752 | else if (ch=='b'){ |
591 | 2376 | out.append('\b'); |
592 | 1188 | } |
593 | 4940 | else if (ch==delimiter){ |
594 | 1876 | out.append('\\'); |
595 | 3064 | out.append(delimiter); |
596 | } |
|
597 | 3055 | else if (ch=='u'){ |
598 | // uh-oh, we're in unicode country.... |
|
599 | 1862 | inUnicode = true; |
600 | 9 | } |
601 | else { |
|
602 | 14 | out.append(ch); |
603 | 9 | } |
604 | ||
605 | 544784 | continue; |
606 | } |
|
607 | 866350 | else if (ch == '\\') |
608 | 7128 | { |
609 | 11256 | hadSlash = true; |
610 | 548898 | continue; |
611 | } |
|
612 | 847966 | out.append(ch); |
613 | 57762 | } |
614 | ||
615 | 91000 | if (hadSlash) |
616 | { |
|
617 | // then we're in the weird case of a \ at the end of the |
|
618 | // string, let's output it anyway. |
|
619 | 0 | out.append('\\'); |
620 | 57762 | } |
621 | ||
622 | 91000 | return out.toString(); |
623 | } |
|
624 | ||
625 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |