001: package net.myvietnam.mvncore.configuration;
002:
003: /* ====================================================================
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowledgement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgement may appear in the software itself,
026: * if and wherever such third-party acknowledgements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import java.io.File;
058: import java.io.FileWriter;
059: import java.io.IOException;
060: import java.io.InputStream;
061: import java.io.InputStreamReader;
062: import java.io.LineNumberReader;
063: import java.io.Reader;
064: import java.io.UnsupportedEncodingException;
065:
066: import java.util.Date;
067: import java.util.Iterator;
068:
069: import org.apache.commons.lang.StringUtils;
070:
071: /**
072: * loads the configuration from a properties file. <p>
073: *
074: * <p>The properties file syntax is explained here:
075: *
076: * <ul>
077: * <li>
078: * Each property has the syntax <code>key = value</code>
079: * </li>
080: * <li>
081: * The <i>key</i> may use any character but the equal sign '='.
082: * </li>
083: * <li>
084: * <i>value</i> may be separated on different lines if a backslash
085: * is placed at the end of the line that continues below.
086: * </li>
087: * <li>
088: * If <i>value</i> is a list of strings, each token is separated
089: * by a comma ','.
090: * </li>
091: * <li>
092: * Commas in each token are escaped placing a backslash right before
093: * the comma.
094: * </li>
095: * <li>
096: * If a <i>key</i> is used more than once, the values are appended
097: * like if they were on the same line separated with commas.
098: * </li>
099: * <li>
100: * Blank lines and lines starting with character '#' are skipped.
101: * </li>
102: * <li>
103: * If a property is named "include" (or whatever is defined by
104: * setInclude() and getInclude() and the value of that property is
105: * the full path to a file on disk, that file will be included into
106: * the ConfigurationsRepository. You can also pull in files relative
107: * to the parent configuration file. So if you have something
108: * like the following:
109: *
110: * include = additional.properties
111: *
112: * Then "additional.properties" is expected to be in the same
113: * directory as the parent configuration file.
114: *
115: * Duplicate name values will be replaced, so be careful.
116: *
117: * </li>
118: * </ul>
119: *
120: * <p>Here is an example of a valid extended properties file:
121: *
122: * <p><pre>
123: * # lines starting with # are comments
124: *
125: * # This is the simplest property
126: * key = value
127: *
128: * # A long property may be separated on multiple lines
129: * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
130: * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
131: *
132: * # This is a property with many tokens
133: * tokens_on_a_line = first token, second token
134: *
135: * # This sequence generates exactly the same result
136: * tokens_on_multiple_lines = first token
137: * tokens_on_multiple_lines = second token
138: *
139: * # commas may be escaped in tokens
140: * commas.excaped = Hi\, what'up?
141: *
142: * # properties can reference other properties
143: * base.prop = /base
144: * first.prop = ${base.prop}/first
145: * second.prop = ${first.prop}/second
146: * </pre>
147: *
148: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
149: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
150: * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
151: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
152: * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
153: * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
154: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
155: * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
156: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
157: * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
158: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
159: * @version $Id: BasePropertiesConfiguration.java,v 1.2 2003/12/10 04:30:45 minhnn Exp $
160: */
161: public abstract class BasePropertiesConfiguration extends
162: BasePathConfiguration {
163: /** Allow file inclusion or not */
164: private boolean includesAllowed = false;
165:
166: /**
167: * This is the name of the property that can point to other
168: * properties file for including other properties files.
169: */
170: protected static String include = "include";
171:
172: /**
173: * Implementations of this class must implement this method.
174: *
175: * @param resourceName The Resource to load
176: * @return An Input Stream
177: *
178: * @throws IOException Error while loading the properties file
179: */
180: protected abstract InputStream getPropertyStream(String resourceName)
181: throws IOException;
182:
183: /**
184: * Load the properties from the given input stream.
185: *
186: * @param input An InputStream.
187: * @throws IOException
188: */
189: public void load(InputStream input) throws IOException {
190: load(input, null);
191: }
192:
193: /**
194: * Load the properties from the given input stream and using the specified
195: * encoding.
196: *
197: * @param input An InputStream.
198: * @param enc An encoding.
199: * @exception IOException
200: */
201: public synchronized void load(InputStream input, String enc)
202: throws IOException {
203: PropertiesReader reader = null;
204: if (enc != null) {
205: try {
206: reader = new PropertiesReader(new InputStreamReader(
207: input, enc));
208: } catch (UnsupportedEncodingException e) {
209: // Get one with the default encoding...
210: }
211: }
212:
213: if (reader == null) {
214: reader = new PropertiesReader(new InputStreamReader(input));
215: }
216:
217: while (true) {
218: String line = reader.readProperty();
219:
220: if (line == null) {
221: break; // EOF
222: }
223:
224: int equalSign = line.indexOf('=');
225: if (equalSign > 0) {
226: String key = line.substring(0, equalSign).trim();
227: String value = line.substring(equalSign + 1).trim();
228:
229: // Though some software (e.g. autoconf) may produce
230: // empty values like foo=\n, emulate the behavior of
231: // java.util.Properties by setting the value to the
232: // empty string.
233:
234: if (StringUtils.isNotEmpty(getInclude())
235: && key.equalsIgnoreCase(getInclude())) {
236: if (getIncludesAllowed()) {
237: String[] files = StringUtils.split(value, ",");
238: for (int cnt = 0; cnt < files.length; cnt++) {
239: load(getPropertyStream(files[cnt].trim()));
240: }
241: }
242: } else {
243: addProperty(key, value);
244: }
245: }
246: }
247: }
248:
249: /**
250: * save properties to a file.
251: * properties with multiple values are saved comma seperated.
252: *
253: * @param filename name of the properties file
254: * @throws IOException
255: */
256: public void save(String filename) throws IOException {
257: File file = new File(filename);
258: PropertiesWriter out = new PropertiesWriter(file);
259:
260: out.writeComment("written by PropertiesConfiguration");
261: out.writeComment(new Date().toString());
262:
263: for (Iterator i = this .getKeys(); i.hasNext();) {
264: String key = (String) i.next();
265: String value = StringUtils.join(this .getStringArray(key),
266: ", ");
267: out.writeProperty(key, value);
268: }
269: out.flush();
270: out.close();
271: }
272:
273: /**
274: * Gets the property value for including other properties files.
275: * By default it is "include".
276: *
277: * @return A String.
278: */
279: public String getInclude() {
280: return BasePropertiesConfiguration.include;
281: }
282:
283: /**
284: * Sets the property value for including other properties files.
285: * By default it is "include".
286: *
287: * @param inc A String.
288: */
289: public void setInclude(String inc) {
290: BasePropertiesConfiguration.include = inc;
291: }
292:
293: /**
294: * Controls whether additional files can be loaded by the include = <xxx>
295: * statement or not. Base rule is, that objects created by the empty
296: * C'tor can not have included files.
297: *
298: * @param includesAllowed includesAllowed True if Includes are allowed.
299: */
300: protected void setIncludesAllowed(boolean includesAllowed) {
301: this .includesAllowed = includesAllowed;
302: }
303:
304: /**
305: * Reports the status of file inclusion.
306: *
307: * @return True if include files are loaded.
308: */
309: public boolean getIncludesAllowed() {
310: return this .includesAllowed;
311: }
312:
313: /**
314: * This class is used to read properties lines. These lines do
315: * not terminate with new-line chars but rather when there is no
316: * backslash sign a the end of the line. This is used to
317: * concatenate multiple lines for readability.
318: */
319: class PropertiesReader extends LineNumberReader {
320: /**
321: * Constructor.
322: *
323: * @param reader A Reader.
324: */
325: public PropertiesReader(Reader reader) {
326: super (reader);
327: }
328:
329: /**
330: * Read a property. Returns null if Stream is
331: * at EOF. Concatenates lines ending with "\".
332: * Skips lines beginning with "#" and empty lines.
333: *
334: * @return A string containing a property value or null
335: *
336: * @exception IOException
337: */
338: public String readProperty() throws IOException {
339: StringBuffer buffer = new StringBuffer();
340:
341: while (true) {
342: String line = readLine();
343: if (line == null) {
344: // EOF
345: return null;
346: }
347:
348: line = line.trim();
349:
350: if (StringUtils.isEmpty(line)
351: || (line.charAt(0) == '#')) {
352: continue;
353: }
354:
355: if (line.endsWith("\\")) {
356: line = line.substring(0, line.length() - 1);
357: buffer.append(line);
358: } else {
359: buffer.append(line);
360: break;
361: }
362: }
363: return buffer.toString();
364: }
365: } // class PropertiesReader
366:
367: /**
368: * This class is used to write properties lines.
369: */
370: class PropertiesWriter extends FileWriter {
371: /**
372: * Constructor.
373: *
374: * @param file the proerties file
375: * @throws IOException
376: */
377: public PropertiesWriter(File file) throws IOException {
378: super (file);
379: }
380:
381: /**
382: * Write a property.
383: *
384: * @param key
385: * @param value
386: * @exception IOException
387: */
388: public void writeProperty(String key, String value)
389: throws IOException {
390: write(key);
391: write(" = ");
392: write(value != null ? value : "");
393: write('\n');
394: }
395:
396: /**
397: * Write a comment.
398: *
399: * @param comment
400: * @exception IOException
401: */
402: public void writeComment(String comment) throws IOException {
403: write("# " + comment + "\n");
404: }
405: } // class PropertiesWriter
406: }
|