%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.commons.configuration.PropertiesConfiguration$PropertiesReader |
|
|
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.BufferedReader; |
|
20 | import java.io.File; |
|
21 | import java.io.FilterWriter; |
|
22 | import java.io.IOException; |
|
23 | import java.io.LineNumberReader; |
|
24 | import java.io.Reader; |
|
25 | import java.io.StringReader; |
|
26 | import java.io.Writer; |
|
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.ArrayUtils; |
|
33 | import org.apache.commons.lang.StringEscapeUtils; |
|
34 | import org.apache.commons.lang.StringUtils; |
|
35 | ||
36 | /** |
|
37 | * This is the "classic" Properties loader which loads the values from |
|
38 | * a single or multiple files (which can be chained with "include =". |
|
39 | * All given path references are either absolute or relative to the |
|
40 | * file name supplied in the constructor. |
|
41 | * <p> |
|
42 | * In this class, empty PropertyConfigurations can be built, properties |
|
43 | * added and later saved. include statements are (obviously) not supported |
|
44 | * if you don't construct a PropertyConfiguration from a file. |
|
45 | * |
|
46 | * <p>The properties file syntax is explained here, basically it follows |
|
47 | * the syntax of the stream parsed by {@link java.util.Properties#load} and |
|
48 | * adds several useful extensions: |
|
49 | * |
|
50 | * <ul> |
|
51 | * <li> |
|
52 | * Each property has the syntax <code>key <separator> value</code>. The |
|
53 | * separators accepted are <code>'='</code>, <code>':'</code> and any white |
|
54 | * space character. Examples: |
|
55 | * <pre> |
|
56 | * key1 = value1 |
|
57 | * key2 : value2 |
|
58 | * key3 value3</pre> |
|
59 | * </li> |
|
60 | * <li> |
|
61 | * The <i>key</i> may use any character, separators must be escaped: |
|
62 | * <pre> |
|
63 | * key\:foo = bar</pre> |
|
64 | * </li> |
|
65 | * <li> |
|
66 | * <i>value</i> may be separated on different lines if a backslash |
|
67 | * is placed at the end of the line that continues below. |
|
68 | * </li> |
|
69 | * <li> |
|
70 | * <i>value</i> can contain <em>value delimiters</em> and will then be interpreted |
|
71 | * as a list of tokens. Default value delimiter is the comma ','. So the |
|
72 | * following property definition |
|
73 | * <pre> |
|
74 | * key = This property, has multiple, values |
|
75 | * </pre> |
|
76 | * will result in a property with three values. You can change the value |
|
77 | * delmiter using the <code>{@link AbstractConfiguration#setDelimiter(char)}</code> |
|
78 | * method. Setting the delimiter to 0 will disable value splitting completely. |
|
79 | * </li> |
|
80 | * <li> |
|
81 | * Commas in each token are escaped placing a backslash right before |
|
82 | * the comma. |
|
83 | * </li> |
|
84 | * <li> |
|
85 | * If a <i>key</i> is used more than once, the values are appended |
|
86 | * like if they were on the same line separated with commas. |
|
87 | * </li> |
|
88 | * <li> |
|
89 | * Blank lines and lines starting with character '#' or '!' are skipped. |
|
90 | * </li> |
|
91 | * <li> |
|
92 | * If a property is named "include" (or whatever is defined by |
|
93 | * setInclude() and getInclude() and the value of that property is |
|
94 | * the full path to a file on disk, that file will be included into |
|
95 | * the configuration. You can also pull in files relative to the parent |
|
96 | * configuration file. So if you have something like the following: |
|
97 | * |
|
98 | * include = additional.properties |
|
99 | * |
|
100 | * Then "additional.properties" is expected to be in the same |
|
101 | * directory as the parent configuration file. |
|
102 | * |
|
103 | * The properties in the included file are added to the parent configuration, |
|
104 | * they do not replace existing properties with the same key. |
|
105 | * |
|
106 | * </li> |
|
107 | * </ul> |
|
108 | * |
|
109 | * <p>Here is an example of a valid extended properties file: |
|
110 | * |
|
111 | * <p><pre> |
|
112 | * # lines starting with # are comments |
|
113 | * |
|
114 | * # This is the simplest property |
|
115 | * key = value |
|
116 | * |
|
117 | * # A long property may be separated on multiple lines |
|
118 | * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ |
|
119 | * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
|
120 | * |
|
121 | * # This is a property with many tokens |
|
122 | * tokens_on_a_line = first token, second token |
|
123 | * |
|
124 | * # This sequence generates exactly the same result |
|
125 | * tokens_on_multiple_lines = first token |
|
126 | * tokens_on_multiple_lines = second token |
|
127 | * |
|
128 | * # commas may be escaped in tokens |
|
129 | * commas.escaped = Hi\, what'up? |
|
130 | * |
|
131 | * # properties can reference other properties |
|
132 | * base.prop = /base |
|
133 | * first.prop = ${base.prop}/first |
|
134 | * second.prop = ${first.prop}/second |
|
135 | * </pre> |
|
136 | * |
|
137 | * @see java.util.Properties#load |
|
138 | * |
|
139 | * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a> |
|
140 | * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> |
|
141 | * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a> |
|
142 | * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
|
143 | * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a> |
|
144 | * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a> |
|
145 | * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> |
|
146 | * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a> |
|
147 | * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> |
|
148 | * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a> |
|
149 | * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> |
|
150 | * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> |
|
151 | * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a> |
|
152 | * @author <a href="mailto:ebourg@apache.org">Emmanuel Bourg</a> |
|
153 | * @version $Id: PropertiesConfiguration.java 354264 2005-12-06 03:10:27Z ebourg $ |
|
154 | */ |
|
155 | public class PropertiesConfiguration extends AbstractFileConfiguration |
|
156 | { |
|
157 | /** |
|
158 | * This is the name of the property that can point to other |
|
159 | * properties file for including other properties files. |
|
160 | */ |
|
161 | private static String include = "include"; |
|
162 | ||
163 | /** The list of possible key/value separators */ |
|
164 | private static final char[] SEPARATORS = new class="keyword">char[] {'=', ':'}; |
|
165 | ||
166 | /** The white space characters used as key/value separators. */ |
|
167 | private static final char[] WHITE_SPACE = new class="keyword">char[]{' ', '\t', '\f'}; |
|
168 | ||
169 | /** |
|
170 | * The default encoding (ISO-8859-1 as specified by |
|
171 | * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html) |
|
172 | */ |
|
173 | private static final String DEFAULT_ENCODING = "ISO-8859-1"; |
|
174 | ||
175 | /** Constant for the platform specific line separator.*/ |
|
176 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); |
|
177 | ||
178 | /** Constant for the radix of hex numbers.*/ |
|
179 | private static final int HEX_RADIX = 16; |
|
180 | ||
181 | /** Constant for the length of a unicode literal.*/ |
|
182 | private static final int UNICODE_LEN = 4; |
|
183 | ||
184 | /** Allow file inclusion or not */ |
|
185 | private boolean includesAllowed; |
|
186 | ||
187 | /** Comment header of the .properties file */ |
|
188 | private String header; |
|
189 | ||
190 | // initialization block to set the encoding before loading the file in the constructors |
|
191 | { |
|
192 | setEncoding(DEFAULT_ENCODING); |
|
193 | } |
|
194 | ||
195 | /** |
|
196 | * Creates an empty PropertyConfiguration object which can be |
|
197 | * used to synthesize a new Properties file by adding values and |
|
198 | * then saving(). |
|
199 | */ |
|
200 | public PropertiesConfiguration() |
|
201 | { |
|
202 | setIncludesAllowed(false); |
|
203 | } |
|
204 | ||
205 | /** |
|
206 | * Creates and loads the extended properties from the specified file. |
|
207 | * The specified file can contain "include = " properties which then |
|
208 | * are loaded and merged into the properties. |
|
209 | * |
|
210 | * @param fileName The name of the properties file to load. |
|
211 | * @throws ConfigurationException Error while loading the properties file |
|
212 | */ |
|
213 | public PropertiesConfiguration(String fileName) throws ConfigurationException |
|
214 | { |
|
215 | super(fileName); |
|
216 | } |
|
217 | ||
218 | /** |
|
219 | * Creates and loads the extended properties from the specified file. |
|
220 | * The specified file can contain "include = " properties which then |
|
221 | * are loaded and merged into the properties. |
|
222 | * |
|
223 | * @param file The properties file to load. |
|
224 | * @throws ConfigurationException Error while loading the properties file |
|
225 | */ |
|
226 | public PropertiesConfiguration(File file) throws ConfigurationException |
|
227 | { |
|
228 | super(file); |
|
229 | } |
|
230 | ||
231 | /** |
|
232 | * Creates and loads the extended properties from the specified URL. |
|
233 | * The specified file can contain "include = " properties which then |
|
234 | * are loaded and merged into the properties. |
|
235 | * |
|
236 | * @param url The location of the properties file to load. |
|
237 | * @throws ConfigurationException Error while loading the properties file |
|
238 | */ |
|
239 | public PropertiesConfiguration(URL url) throws ConfigurationException |
|
240 | { |
|
241 | super(url); |
|
242 | } |
|
243 | ||
244 | /** |
|
245 | * Gets the property value for including other properties files. |
|
246 | * By default it is "include". |
|
247 | * |
|
248 | * @return A String. |
|
249 | */ |
|
250 | public static String getInclude() |
|
251 | { |
|
252 | return PropertiesConfiguration.include; |
|
253 | } |
|
254 | ||
255 | /** |
|
256 | * Sets the property value for including other properties files. |
|
257 | * By default it is "include". |
|
258 | * |
|
259 | * @param inc A String. |
|
260 | */ |
|
261 | public static void setInclude(String inc) |
|
262 | { |
|
263 | PropertiesConfiguration.include = inc; |
|
264 | } |
|
265 | ||
266 | /** |
|
267 | * Controls whether additional files can be loaded by the include = <xxx> |
|
268 | * statement or not. Base rule is, that objects created by the empty |
|
269 | * C'tor can not have included files. |
|
270 | * |
|
271 | * @param includesAllowed includesAllowed True if Includes are allowed. |
|
272 | */ |
|
273 | protected void setIncludesAllowed(boolean includesAllowed) |
|
274 | { |
|
275 | this.includesAllowed = includesAllowed; |
|
276 | } |
|
277 | ||
278 | /** |
|
279 | * Reports the status of file inclusion. |
|
280 | * |
|
281 | * @return True if include files are loaded. |
|
282 | */ |
|
283 | public boolean getIncludesAllowed() |
|
284 | { |
|
285 | return this.includesAllowed; |
|
286 | } |
|
287 | ||
288 | /** |
|
289 | * Return the comment header. |
|
290 | * |
|
291 | * @return the comment header |
|
292 | * @since 1.1 |
|
293 | */ |
|
294 | public String getHeader() |
|
295 | { |
|
296 | return header; |
|
297 | } |
|
298 | ||
299 | /** |
|
300 | * Set the comment header. |
|
301 | * |
|
302 | * @param header the header to use |
|
303 | * @since 1.1 |
|
304 | */ |
|
305 | public void setHeader(String header) |
|
306 | { |
|
307 | this.header = header; |
|
308 | } |
|
309 | ||
310 | /** |
|
311 | * Load the properties from the given reader. |
|
312 | * Note that the <code>clear()</code> method is not called, so |
|
313 | * the properties contained in the loaded file will be added to the |
|
314 | * actual set of properties. |
|
315 | * |
|
316 | * @param in An InputStream. |
|
317 | * |
|
318 | * @throws ConfigurationException if an error occurs |
|
319 | */ |
|
320 | public synchronized void load(Reader in) throws ConfigurationException |
|
321 | { |
|
322 | PropertiesReader reader = new PropertiesReader(in); |
|
323 | boolean oldAutoSave = isAutoSave(); |
|
324 | setAutoSave(false); |
|
325 | ||
326 | try |
|
327 | { |
|
328 | while (true) |
|
329 | { |
|
330 | String line = reader.readProperty(); |
|
331 | ||
332 | if (line == null) |
|
333 | { |
|
334 | break; // EOF |
|
335 | } |
|
336 | ||
337 | // parse the line |
|
338 | String[] property = parseProperty(line); |
|
339 | String key = property[0]; |
|
340 | String value = property[1]; |
|
341 | ||
342 | // Though some software (e.g. autoconf) may produce |
|
343 | // empty values like foo=\n, emulate the behavior of |
|
344 | // java.util.Properties by setting the value to the |
|
345 | // empty string. |
|
346 | ||
347 | if (StringUtils.isNotEmpty(getInclude()) && key.equalsIgnoreCase(getInclude())) |
|
348 | { |
|
349 | if (getIncludesAllowed()) |
|
350 | { |
|
351 | String [] files = StringUtils.split(value, getDelimiter()); |
|
352 | for (int i = 0; i < files.length; i++) |
|
353 | { |
|
354 | loadIncludeFile(files[i].trim()); |
|
355 | } |
|
356 | } |
|
357 | } |
|
358 | else |
|
359 | { |
|
360 | addProperty(StringEscapeUtils.unescapeJava(key), unescapeJava(value, getDelimiter())); |
|
361 | } |
|
362 | ||
363 | } |
|
364 | } |
|
365 | catch (IOException ioe) |
|
366 | { |
|
367 | throw new ConfigurationException("Could not load configuration from input stream.", ioe); |
|
368 | } |
|
369 | finally |
|
370 | { |
|
371 | setAutoSave(oldAutoSave); |
|
372 | } |
|
373 | } |
|
374 | ||
375 | /** |
|
376 | * Save the configuration to the specified stream. |
|
377 | * |
|
378 | * @param writer the output stream used to save the configuration |
|
379 | * @throws ConfigurationException if an error occurs |
|
380 | */ |
|
381 | public void save(Writer writer) throws ConfigurationException |
|
382 | { |
|
383 | enterNoReload(); |
|
384 | try |
|
385 | { |
|
386 | PropertiesWriter out = new PropertiesWriter(writer, getDelimiter()); |
|
387 | ||
388 | if (header != null) |
|
389 | { |
|
390 | BufferedReader reader = new BufferedReader(class="keyword">new StringReader(header)); |
|
391 | String line; |
|
392 | while ((line = reader.readLine()) != null) |
|
393 | { |
|
394 | out.writeComment(line); |
|
395 | } |
|
396 | out.writeln(null); |
|
397 | } |
|
398 | ||
399 | out.writeComment("written by PropertiesConfiguration"); |
|
400 | out.writeComment(new Date().toString()); |
|
401 | out.writeln(null); |
|
402 | ||
403 | Iterator keys = getKeys(); |
|
404 | while (keys.hasNext()) |
|
405 | { |
|
406 | String key = (String) keys.next(); |
|
407 | Object value = getProperty(key); |
|
408 | ||
409 | if (value instanceof List) |
|
410 | { |
|
411 | out.writeProperty(key, (List) value); |
|
412 | } |
|
413 | else |
|
414 | { |
|
415 | out.writeProperty(key, value); |
|
416 | } |
|
417 | } |
|
418 | ||
419 | out.flush(); |
|
420 | } |
|
421 | catch (IOException e) |
|
422 | { |
|
423 | throw new ConfigurationException(e.getMessage(), e); |
|
424 | } |
|
425 | finally |
|
426 | { |
|
427 | exitNoReload(); |
|
428 | } |
|
429 | } |
|
430 | ||
431 | /** |
|
432 | * Extend the setBasePath method to turn includes |
|
433 | * on and off based on the existence of a base path. |
|
434 | * |
|
435 | * @param basePath The new basePath to set. |
|
436 | */ |
|
437 | public void setBasePath(String basePath) |
|
438 | { |
|
439 | super.setBasePath(basePath); |
|
440 | setIncludesAllowed(StringUtils.isNotEmpty(basePath)); |
|
441 | } |
|
442 | ||
443 | /** |
|
444 | * This class is used to read properties lines. These lines do |
|
445 | * not terminate with new-line chars but rather when there is no |
|
446 | * backslash sign a the end of the line. This is used to |
|
447 | * concatenate multiple lines for readability. |
|
448 | */ |
|
449 | public static class PropertiesReader extends LineNumberReader |
|
450 | { |
|
451 | /** |
|
452 | * Constructor. |
|
453 | * |
|
454 | * @param reader A Reader. |
|
455 | */ |
|
456 | public PropertiesReader(Reader reader) |
|
457 | { |
|
458 | 1161 | super(reader); |
459 | 1161 | } |
460 | ||
461 | /** |
|
462 | * Read a property. Returns null if Stream is |
|
463 | * at EOF. Concatenates lines ending with "\". |
|
464 | * Skips lines beginning with "#" or "!" and empty lines. |
|
465 | * |
|
466 | * @return A string containing a property value or null |
|
467 | * |
|
468 | * @throws IOException in case of an I/O error |
|
469 | */ |
|
470 | public String readProperty() throws IOException |
|
471 | { |
|
472 | 28377 | StringBuffer buffer = new StringBuffer(); |
473 | ||
474 | 29691 | while (true) |
475 | { |
|
476 | 54153 | String line = readLine(); |
477 | 54153 | if (line == null) |
478 | { |
|
479 | // EOF |
|
480 | 1158 | return null; |
481 | } |
|
482 | ||
483 | 52995 | line = line.trim(); |
484 | ||
485 | // skip comments and empty lines |
|
486 | 52995 | if (StringUtils.isEmpty(line) || (line.charAt(0) == '#') || (line.charAt(0) == '!')) |
487 | { |
|
488 | 438 | continue; |
489 | } |
|
490 | ||
491 | 28533 | if (checkCombineLines(line)) |
492 | { |
|
493 | 1314 | line = line.substring(0, line.length() - 1); |
494 | 1314 | buffer.append(line); |
495 | } |
|
496 | else |
|
497 | { |
|
498 | 27219 | buffer.append(line); |
499 | 27219 | break; |
500 | } |
|
501 | } |
|
502 | 27219 | return buffer.toString(); |
503 | } |
|
504 | ||
505 | /** |
|
506 | * Checks if the passed in line should be combined with the following. |
|
507 | * This is true, if the line ends with an odd number of backslashes. |
|
508 | * |
|
509 | * @param line the line |
|
510 | * @return a flag if the lines should be combined |
|
511 | */ |
|
512 | private static boolean checkCombineLines(String line) |
|
513 | { |
|
514 | 28533 | int bsCount = 0; |
515 | 33369 | for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) |
516 | { |
|
517 | 4836 | bsCount++; |
518 | } |
|
519 | ||
520 | 28533 | return bsCount % 2 == 1; |
521 | } |
|
522 | } // class PropertiesReader |
|
523 | ||
524 | /** |
|
525 | * This class is used to write properties lines. |
|
526 | */ |
|
527 | public static class PropertiesWriter extends FilterWriter |
|
528 | { |
|
529 | /** The delimiter for multi-valued properties.*/ |
|
530 | private char delimiter; |
|
531 | ||
532 | /** |
|
533 | * Constructor. |
|
534 | * |
|
535 | * @param writer a Writer object providing the underlying stream |
|
536 | * @param delimiter the delimiter character for multi-valued properties |
|
537 | */ |
|
538 | public PropertiesWriter(Writer writer, char delimiter) |
|
539 | { |
|
540 | super(writer); |
|
541 | this.delimiter = delimiter; |
|
542 | } |
|
543 | ||
544 | /** |
|
545 | * Write a property. |
|
546 | * |
|
547 | * @param key the key of the property |
|
548 | * @param value the value of the property |
|
549 | * |
|
550 | * @throws IOException if an I/O error occurs |
|
551 | */ |
|
552 | public void writeProperty(String key, Object value) throws IOException |
|
553 | { |
|
554 | write(escapeKey(key)); |
|
555 | write(" = "); |
|
556 | if (value != null) |
|
557 | { |
|
558 | String v = StringEscapeUtils.escapeJava(String.valueOf(value)); |
|
559 | v = StringUtils.replace(v, String.valueOf(delimiter), "\\" + delimiter); |
|
560 | write(v); |
|
561 | } |
|
562 | ||
563 | writeln(null); |
|
564 | } |
|
565 | ||
566 | /** |
|
567 | * Write a property. |
|
568 | * |
|
569 | * @param key The key of the property |
|
570 | * @param values The array of values of the property |
|
571 | * |
|
572 | * @throws IOException if an I/O error occurs |
|
573 | */ |
|
574 | public void writeProperty(String key, List values) throws IOException |
|
575 | { |
|
576 | for (int i = 0; i < values.size(); i++) |
|
577 | { |
|
578 | writeProperty(key, values.get(i)); |
|
579 | } |
|
580 | } |
|
581 | ||
582 | /** |
|
583 | * Write a comment. |
|
584 | * |
|
585 | * @param comment the comment to write |
|
586 | * @throws IOException if an I/O error occurs |
|
587 | */ |
|
588 | public void writeComment(String comment) throws IOException |
|
589 | { |
|
590 | writeln("# " + comment); |
|
591 | } |
|
592 | ||
593 | /** |
|
594 | * Escape the separators in the key. |
|
595 | * |
|
596 | * @param key the key |
|
597 | * @return the escaped key |
|
598 | * @since 1.2 |
|
599 | */ |
|
600 | private String escapeKey(String key) |
|
601 | { |
|
602 | StringBuffer newkey = new StringBuffer(); |
|
603 | ||
604 | for (int i = 0; i < key.length(); i++) |
|
605 | { |
|
606 | char c = key.class="keyword">charAt(i); |
|
607 | ||
608 | if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c)) |
|
609 | { |
|
610 | // escape the separator |
|
611 | newkey.append('\\'); |
|
612 | newkey.append(c); |
|
613 | } |
|
614 | else |
|
615 | { |
|
616 | newkey.append(c); |
|
617 | } |
|
618 | } |
|
619 | ||
620 | return newkey.toString(); |
|
621 | } |
|
622 | ||
623 | /** |
|
624 | * Helper method for writing a line with the platform specific line |
|
625 | * ending. |
|
626 | * |
|
627 | * @param s the content of the line (may be <b>null</b>) |
|
628 | * @throws IOException if an error occurs |
|
629 | */ |
|
630 | private void writeln(String s) throws IOException |
|
631 | { |
|
632 | if (s != null) |
|
633 | { |
|
634 | write(s); |
|
635 | } |
|
636 | write(LINE_SEPARATOR); |
|
637 | } |
|
638 | ||
639 | } // class PropertiesWriter |
|
640 | ||
641 | /** |
|
642 | * <p>Unescapes any Java literals found in the <code>String</code> to a |
|
643 | * <code>Writer</code>.</p> This is a slightly modified version of the |
|
644 | * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't |
|
645 | * drop escaped separators (i.e '\,'). |
|
646 | * |
|
647 | * @param str the <code>String</code> to unescape, may be null |
|
648 | * @param delimiter the delimiter for multi-valued properties |
|
649 | * @return the processed string |
|
650 | * @throws IllegalArgumentException if the Writer is <code>null</code> |
|
651 | */ |
|
652 | protected static String unescapeJava(String str, char delimiter) |
|
653 | { |
|
654 | if (str == null) |
|
655 | { |
|
656 | return null; |
|
657 | } |
|
658 | int sz = str.length(); |
|
659 | StringBuffer out = new StringBuffer(sz); |
|
660 | StringBuffer unicode = new StringBuffer(UNICODE_LEN); |
|
661 | boolean hadSlash = false; |
|
662 | boolean inUnicode = false; |
|
663 | for (int i = 0; i < sz; i++) |
|
664 | { |
|
665 | char ch = str.class="keyword">charAt(i); |
|
666 | if (inUnicode) |
|
667 | { |
|
668 | // if in unicode, then we're reading unicode |
|
669 | // values in somehow |
|
670 | unicode.append(ch); |
|
671 | if (unicode.length() == UNICODE_LEN) |
|
672 | { |
|
673 | // unicode now contains the four hex digits |
|
674 | // which represents our unicode character |
|
675 | try |
|
676 | { |
|
677 | int value = Integer.parseInt(unicode.toString(), HEX_RADIX); |
|
678 | out.append((char) value); |
|
679 | unicode.setLength(0); |
|
680 | inUnicode = false; |
|
681 | hadSlash = false; |
|
682 | } |
|
683 | catch (NumberFormatException nfe) |
|
684 | { |
|
685 | throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe); |
|
686 | } |
|
687 | } |
|
688 | continue; |
|
689 | } |
|
690 | ||
691 | if (hadSlash) |
|
692 | { |
|
693 | // handle an escaped value |
|
694 | hadSlash = false; |
|
695 | ||
696 | if (ch == '\\') |
|
697 | { |
|
698 | out.append('\\'); |
|
699 | } |
|
700 | else if (ch == '\'') |
|
701 | { |
|
702 | out.append('\''); |
|
703 | } |
|
704 | else if (ch == '\"') |
|
705 | { |
|
706 | out.append('"'); |
|
707 | } |
|
708 | else if (ch == 'r') |
|
709 | { |
|
710 | out.append('\r'); |
|
711 | } |
|
712 | else if (ch == 'f') |
|
713 | { |
|
714 | out.append('\f'); |
|
715 | } |
|
716 | else if (ch == 't') |
|
717 | { |
|
718 | out.append('\t'); |
|
719 | } |
|
720 | else if (ch == 'n') |
|
721 | { |
|
722 | out.append('\n'); |
|
723 | } |
|
724 | else if (ch == 'b') |
|
725 | { |
|
726 | out.append('\b'); |
|
727 | } |
|
728 | else if (ch == delimiter) |
|
729 | { |
|
730 | out.append('\\'); |
|
731 | out.append(delimiter); |
|
732 | } |
|
733 | else if (ch == 'u') |
|
734 | { |
|
735 | // uh-oh, we're in unicode country.... |
|
736 | inUnicode = true; |
|
737 | } |
|
738 | else |
|
739 | { |
|
740 | out.append(ch); |
|
741 | } |
|
742 | ||
743 | continue; |
|
744 | } |
|
745 | else if (ch == '\\') |
|
746 | { |
|
747 | hadSlash = true; |
|
748 | continue; |
|
749 | } |
|
750 | out.append(ch); |
|
751 | } |
|
752 | ||
753 | if (hadSlash) |
|
754 | { |
|
755 | // then we're in the weird case of a \ at the end of the |
|
756 | // string, let's output it anyway. |
|
757 | out.append('\\'); |
|
758 | } |
|
759 | ||
760 | return out.toString(); |
|
761 | } |
|
762 | ||
763 | /** |
|
764 | * Parse a property line and return the key and the value in an array. |
|
765 | * |
|
766 | * @param line the line to parse |
|
767 | * @return an array with the property's key and value |
|
768 | * @since 1.2 |
|
769 | */ |
|
770 | private String[] parseProperty(String line) |
|
771 | { |
|
772 | // sorry for this spaghetti code, please replace it as soon as |
|
773 | // possible with a regexp when the Java 1.3 requirement is dropped |
|
774 | ||
775 | String[] result = new String[2]; |
|
776 | StringBuffer key = new StringBuffer(); |
|
777 | StringBuffer value = new StringBuffer(); |
|
778 | ||
779 | // state of the automaton: |
|
780 | // 0: key parsing |
|
781 | // 1: antislash found while parsing the key |
|
782 | // 2: separator crossing |
|
783 | // 3: value parsing |
|
784 | int state = 0; |
|
785 | ||
786 | for (int pos = 0; pos < line.length(); pos++) |
|
787 | { |
|
788 | char c = line.class="keyword">charAt(pos); |
|
789 | ||
790 | switch (state) |
|
791 | { |
|
792 | case 0: |
|
793 | if (c == '\\') |
|
794 | { |
|
795 | state = 1; |
|
796 | } |
|
797 | else if (ArrayUtils.contains(WHITE_SPACE, c)) |
|
798 | { |
|
799 | // switch to the separator crossing state |
|
800 | state = 2; |
|
801 | } |
|
802 | else if (ArrayUtils.contains(SEPARATORS, c)) |
|
803 | { |
|
804 | // switch to the value parsing state |
|
805 | state = 3; |
|
806 | } |
|
807 | else |
|
808 | { |
|
809 | key.append(c); |
|
810 | } |
|
811 | ||
812 | break; |
|
813 | ||
814 | case 1: |
|
815 | if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c)) |
|
816 | { |
|
817 | // this is an escaped separator or white space |
|
818 | key.append(c); |
|
819 | } |
|
820 | else |
|
821 | { |
|
822 | // another escaped character, the '\' is preserved |
|
823 | key.append('\\'); |
|
824 | key.append(c); |
|
825 | } |
|
826 | ||
827 | // return to the key parsing state |
|
828 | state = 0; |
|
829 | ||
830 | break; |
|
831 | ||
832 | case 2: |
|
833 | if (ArrayUtils.contains(WHITE_SPACE, c)) |
|
834 | { |
|
835 | // do nothing, eat all white spaces |
|
836 | state = 2; |
|
837 | } |
|
838 | else if (ArrayUtils.contains(SEPARATORS, c)) |
|
839 | { |
|
840 | // switch to the value parsing state |
|
841 | state = 3; |
|
842 | } |
|
843 | else |
|
844 | { |
|
845 | // any other character indicates we encoutered the beginning of the value |
|
846 | value.append(c); |
|
847 | ||
848 | // switch to the value parsing state |
|
849 | state = 3; |
|
850 | } |
|
851 | ||
852 | break; |
|
853 | ||
854 | case 3: |
|
855 | value.append(c); |
|
856 | break; |
|
857 | } |
|
858 | } |
|
859 | ||
860 | result[0] = key.toString().trim(); |
|
861 | result[1] = value.toString().trim(); |
|
862 | ||
863 | return result; |
|
864 | } |
|
865 | ||
866 | /** |
|
867 | * Helper method for loading an included properties file. This method is |
|
868 | * called by <code>load()</code> when an <code>include</code> property |
|
869 | * is encountered. It tries to resolve relative file names based on the |
|
870 | * current base path. If this fails, a resolution based on the location of |
|
871 | * this properties file is tried. |
|
872 | * |
|
873 | * @param fileName the name of the file to load |
|
874 | * @throws ConfigurationException if loading fails |
|
875 | */ |
|
876 | private void loadIncludeFile(String fileName) throws ConfigurationException |
|
877 | { |
|
878 | URL url = ConfigurationUtils.locate(getBasePath(), fileName); |
|
879 | if (url == null) |
|
880 | { |
|
881 | URL baseURL = getURL(); |
|
882 | if (baseURL != null) |
|
883 | { |
|
884 | url = ConfigurationUtils.locate(baseURL.toString(), fileName); |
|
885 | } |
|
886 | } |
|
887 | ||
888 | if (url == null) |
|
889 | { |
|
890 | throw new ConfigurationException("Cannot resolve include file " |
|
891 | + fileName); |
|
892 | } |
|
893 | load(url); |
|
894 | } |
|
895 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |