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