KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > 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 JavaDoc;
20 import java.io.FilterWriter JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.LineNumberReader JavaDoc;
23 import java.io.Reader JavaDoc;
24 import java.io.Writer JavaDoc;
25 import java.io.StringReader JavaDoc;
26 import java.io.BufferedReader JavaDoc;
27 import java.net.URL JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
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     static String JavaDoc include = "include";
142
143     /** Allow file inclusion or not */
144     private boolean includesAllowed = true;
145
146     /** Comment header of the .properties file */
147     private String JavaDoc 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     {
158         setIncludesAllowed(false);
159     }
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 JavaDoc fileName) throws ConfigurationException
170     {
171         super(fileName);
172     }
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 JavaDoc file) throws ConfigurationException
183     {
184         super(file);
185     }
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 JavaDoc url) throws ConfigurationException
196     {
197         super(url);
198     }
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 JavaDoc getInclude()
207     {
208         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 JavaDoc inc)
218     {
219         PropertiesConfiguration.include = inc;
220     }
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         this.includesAllowed = includesAllowed;
232     }
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         return this.includesAllowed;
242     }
243
244     /**
245      * Return the comment header.
246      *
247      * @since 1.1
248      */

249     public String JavaDoc getHeader()
250     {
251         return header;
252     }
253
254     /**
255      * Set the comment header.
256      *
257      * @since 1.1
258      */

259     public void setHeader(String JavaDoc header)
260     {
261         this.header = header;
262     }
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     public synchronized void load(Reader JavaDoc in) throws ConfigurationException
275     {
276         PropertiesReader reader = new PropertiesReader(in);
277
278         try
279         {
280             while (true)
281             {
282                 String JavaDoc line = reader.readProperty();
283
284                 if (line == null)
285                 {
286                     break; // EOF
287
}
288
289                 int equalSign = line.indexOf('=');
290                 if (equalSign > 0)
291                 {
292                     String JavaDoc key = line.substring(0, equalSign).trim();
293                     String JavaDoc 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                     if (StringUtils.isNotEmpty(getInclude())
301                         && key.equalsIgnoreCase(getInclude()))
302                     {
303                         if (getIncludesAllowed())
304                         {
305                             String JavaDoc [] files = StringUtils.split(value, getDelimiter());
306                             for (int i = 0; i < files.length; i++)
307                             {
308                                 load(ConfigurationUtils.locate(getBasePath(), files[i].trim()));
309                             }
310                         }
311                     }
312                     else
313                     {
314                         addProperty(key, unescapeJava(value, getDelimiter()));
315                     }
316                 }
317             }
318         }
319         catch (IOException JavaDoc ioe)
320         {
321             throw new ConfigurationException("Could not load configuration from input stream.", ioe);
322         }
323     }
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 JavaDoc writer) throws ConfigurationException
331     {
332         try
333         {
334             PropertiesWriter out = new PropertiesWriter(writer, getDelimiter());
335
336             if (header != null)
337             {
338                 BufferedReader JavaDoc reader = new BufferedReader JavaDoc(new StringReader JavaDoc(header));
339                 String JavaDoc line;
340                 while ((line = reader.readLine()) != null)
341                 {
342                     out.writeComment(line);
343                 }
344                 out.write("\n");
345             }
346
347             out.writeComment("written by PropertiesConfiguration");
348             out.writeComment(new Date JavaDoc().toString());
349             out.write("\n");
350
351             Iterator JavaDoc keys = getKeys();
352             while (keys.hasNext())
353             {
354                 String JavaDoc key = (String JavaDoc) keys.next();
355                 Object JavaDoc value = getProperty(key);
356
357                 if (value instanceof List JavaDoc)
358                 {
359                     out.writeProperty(key, (List JavaDoc) value);
360                 }
361                 else
362                 {
363                     out.writeProperty(key, value);
364                 }
365             }
366
367             out.flush();
368         }
369         catch (IOException JavaDoc e)
370         {
371             throw new ConfigurationException(e.getMessage(), e);
372         }
373     }
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     public void setBasePath(String JavaDoc basePath)
382     {
383         super.setBasePath(basePath);
384         setIncludesAllowed(StringUtils.isNotEmpty(basePath));
385     }
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 JavaDoc
394     {
395         /**
396          * Constructor.
397          *
398          * @param reader A Reader.
399          */

400         public PropertiesReader(Reader JavaDoc 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 JavaDoc readProperty() throws IOException JavaDoc
415         {
416             StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
417
418             while (true)
419             {
420                 String JavaDoc 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 JavaDoc
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 JavaDoc 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 JavaDoc key, Object JavaDoc value) throws IOException JavaDoc
476         {
477             write(key);
478             write(" = ");
479             if (value != null)
480             {
481                 String JavaDoc 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 JavaDoc key, List JavaDoc values) throws IOException JavaDoc
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 JavaDoc comment) throws IOException JavaDoc
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     protected static String JavaDoc unescapeJava(String JavaDoc str, char delimiter)
526     {
527         if (str == null)
528         {
529             return null;
530         }
531         int sz = str.length();
532         StringBuffer JavaDoc out = new StringBuffer JavaDoc(sz);
533         StringBuffer JavaDoc unicode = new StringBuffer JavaDoc(4);
534         boolean hadSlash = false;
535         boolean inUnicode = false;
536         for (int i = 0; i < sz; i++)
537         {
538             char ch = str.charAt(i);
539             if (inUnicode)
540             {
541                 // if in unicode, then we're reading unicode
542
// values in somehow
543
unicode.append(ch);
544                 if (unicode.length() == 4)
545                 {
546                     // unicode now contains the four hex digits
547
// which represents our unicode character
548
try
549                     {
550                         int value = Integer.parseInt(unicode.toString(), 16);
551                         out.append((char) value);
552                         unicode.setLength(0);
553                         inUnicode = false;
554                         hadSlash = false;
555                     }
556                     catch (NumberFormatException JavaDoc nfe)
557                     {
558                         throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);
559                     }
560                 }
561                 continue;
562             }
563
564             if (hadSlash)
565             {
566                 // handle an escaped value
567
hadSlash = false;
568
569                 if (ch=='\\'){
570                     out.append('\\');
571                 }
572                 else if (ch=='\''){
573                     out.append('\'');
574                 }
575                 else if (ch=='\"'){
576                     out.append('"');
577                 }
578                 else if (ch=='r'){
579                     out.append('\r');
580                 }
581                 else if (ch=='f'){
582                     out.append('\f');
583                 }
584                 else if (ch=='t'){
585                     out.append('\t');
586                 }
587                 else if (ch=='n'){
588                     out.append('\n');
589                 }
590                 else if (ch=='b'){
591                     out.append('\b');
592                 }
593                 else if (ch==delimiter){
594                     out.append('\\');
595                     out.append(delimiter);
596                 }
597                 else if (ch=='u'){
598                     // uh-oh, we're in unicode country....
599
inUnicode = true;
600                 }
601                 else {
602                     out.append(ch);
603                 }
604
605                 continue;
606             }
607             else if (ch == '\\')
608             {
609                 hadSlash = true;
610                 continue;
611             }
612             out.append(ch);
613         }
614
615         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
out.append('\\');
620         }
621
622         return out.toString();
623     }
624
625 }
626
Popular Tags