0001: /*
0002: * Copyright 2001-2006 The Apache Software Foundation
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.apache.commons.collections;
0017:
0018: import java.io.File;
0019: import java.io.FileInputStream;
0020: import java.io.IOException;
0021: import java.io.InputStream;
0022: import java.io.InputStreamReader;
0023: import java.io.LineNumberReader;
0024: import java.io.OutputStream;
0025: import java.io.PrintWriter;
0026: import java.io.Reader;
0027: import java.io.UnsupportedEncodingException;
0028: import java.util.ArrayList;
0029: import java.util.Enumeration;
0030: import java.util.Hashtable;
0031: import java.util.Iterator;
0032: import java.util.List;
0033: import java.util.NoSuchElementException;
0034: import java.util.Properties;
0035: import java.util.StringTokenizer;
0036: import java.util.Vector;
0037:
0038: /**
0039: * This class extends normal Java properties by adding the possibility
0040: * to use the same key many times concatenating the value strings
0041: * instead of overwriting them.
0042: * <p>
0043: * <b>Please consider using the <code>PropertiesConfiguration</code> class in
0044: * Commons-Configuration as soon as it is released.</b>
0045: * <p>
0046: * The Extended Properties syntax is explained here:
0047: *
0048: * <ul>
0049: * <li>
0050: * Each property has the syntax <code>key = value</code>
0051: * </li>
0052: * <li>
0053: * The <i>key</i> may use any character but the equal sign '='.
0054: * </li>
0055: * <li>
0056: * <i>value</i> may be separated on different lines if a backslash
0057: * is placed at the end of the line that continues below.
0058: * </li>
0059: * <li>
0060: * If <i>value</i> is a list of strings, each token is separated
0061: * by a comma ','.
0062: * </li>
0063: * <li>
0064: * Commas in each token are escaped placing a backslash right before
0065: * the comma.
0066: * </li>
0067: * <li>
0068: * Backslashes are escaped by using two consecutive backslashes i.e. \\
0069: * </li>
0070: * <li>
0071: * If a <i>key</i> is used more than once, the values are appended
0072: * as if they were on the same line separated with commas.
0073: * </li>
0074: * <li>
0075: * Blank lines and lines starting with character '#' are skipped.
0076: * </li>
0077: * <li>
0078: * If a property is named "include" (or whatever is defined by
0079: * setInclude() and getInclude() and the value of that property is
0080: * the full path to a file on disk, that file will be included into
0081: * the ConfigurationsRepository. You can also pull in files relative
0082: * to the parent configuration file. So if you have something
0083: * like the following:
0084: *
0085: * include = additional.properties
0086: *
0087: * Then "additional.properties" is expected to be in the same
0088: * directory as the parent configuration file.
0089: *
0090: * Duplicate name values will be replaced, so be careful.
0091: *
0092: * </li>
0093: * </ul>
0094: *
0095: * <p>Here is an example of a valid extended properties file:
0096: *
0097: * <p><pre>
0098: * # lines starting with # are comments
0099: *
0100: * # This is the simplest property
0101: * key = value
0102: *
0103: * # A long property may be separated on multiple lines
0104: * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
0105: * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0106: *
0107: * # This is a property with many tokens
0108: * tokens_on_a_line = first token, second token
0109: *
0110: * # This sequence generates exactly the same result
0111: * tokens_on_multiple_lines = first token
0112: * tokens_on_multiple_lines = second token
0113: *
0114: * # commas may be escaped in tokens
0115: * commas.escaped = Hi\, what'up?
0116: * </pre>
0117: *
0118: * <p><b>NOTE</b>: this class has <b>not</b> been written for
0119: * performance nor low memory usage. In fact, it's way slower than it
0120: * could be and generates too much memory garbage. But since
0121: * performance is not an issue during intialization (and there is not
0122: * much time to improve it), I wrote it this way. If you don't like
0123: * it, go ahead and tune it up!
0124: *
0125: * @since Commons Collections 1.0
0126: * @version $Revision: 405927 $ $Date: 2006-05-12 23:57:03 +0100 (Fri, 12 May 2006) $
0127: *
0128: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
0129: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
0130: * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
0131: * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
0132: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
0133: * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
0134: * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
0135: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
0136: * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
0137: * @author Janek Bogucki
0138: * @author Mohan Kishore
0139: * @author Stephen Colebourne
0140: * @author Shinobu Kawai
0141: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
0142: */
0143: public class ExtendedProperties extends Hashtable {
0144:
0145: /**
0146: * Default configurations repository.
0147: */
0148: private ExtendedProperties defaults;
0149:
0150: /**
0151: * The file connected to this repository (holding comments and
0152: * such).
0153: *
0154: * @serial
0155: */
0156: protected String file;
0157:
0158: /**
0159: * Base path of the configuration file used to create
0160: * this ExtendedProperties object.
0161: */
0162: protected String basePath;
0163:
0164: /**
0165: * File separator.
0166: */
0167: protected String fileSeparator = System
0168: .getProperty("file.separator");
0169:
0170: /**
0171: * Has this configuration been intialized.
0172: */
0173: protected boolean isInitialized = false;
0174:
0175: /**
0176: * This is the name of the property that can point to other
0177: * properties file for including other properties files.
0178: */
0179: protected static String include = "include";
0180:
0181: /**
0182: * These are the keys in the order they listed
0183: * in the configuration file. This is useful when
0184: * you wish to perform operations with configuration
0185: * information in a particular order.
0186: */
0187: protected ArrayList keysAsListed = new ArrayList();
0188:
0189: protected final static String START_TOKEN = "${";
0190: protected final static String END_TOKEN = "}";
0191:
0192: /**
0193: * Interpolate key names to handle ${key} stuff
0194: *
0195: * @param base string to interpolate
0196: * @return returns the key name with the ${key} substituted
0197: */
0198: protected String interpolate(String base) {
0199: // COPIED from [configuration] 2003-12-29
0200: return (interpolateHelper(base, null));
0201: }
0202:
0203: /**
0204: * Recursive handler for multiple levels of interpolation.
0205: *
0206: * When called the first time, priorVariables should be null.
0207: *
0208: * @param base string with the ${key} variables
0209: * @param priorVariables serves two purposes: to allow checking for
0210: * loops, and creating a meaningful exception message should a loop
0211: * occur. It's 0'th element will be set to the value of base from
0212: * the first call. All subsequent interpolated variables are added
0213: * afterward.
0214: *
0215: * @return the string with the interpolation taken care of
0216: */
0217: protected String interpolateHelper(String base, List priorVariables) {
0218: // COPIED from [configuration] 2003-12-29
0219: if (base == null) {
0220: return null;
0221: }
0222:
0223: // on the first call initialize priorVariables
0224: // and add base as the first element
0225: if (priorVariables == null) {
0226: priorVariables = new ArrayList();
0227: priorVariables.add(base);
0228: }
0229:
0230: int begin = -1;
0231: int end = -1;
0232: int prec = 0 - END_TOKEN.length();
0233: String variable = null;
0234: StringBuffer result = new StringBuffer();
0235:
0236: // FIXME: we should probably allow the escaping of the start token
0237: while (((begin = base.indexOf(START_TOKEN, prec
0238: + END_TOKEN.length())) > -1)
0239: && ((end = base.indexOf(END_TOKEN, begin)) > -1)) {
0240: result.append(base.substring(prec + END_TOKEN.length(),
0241: begin));
0242: variable = base
0243: .substring(begin + START_TOKEN.length(), end);
0244:
0245: // if we've got a loop, create a useful exception message and throw
0246: if (priorVariables.contains(variable)) {
0247: String initialBase = priorVariables.remove(0)
0248: .toString();
0249: priorVariables.add(variable);
0250: StringBuffer priorVariableSb = new StringBuffer();
0251:
0252: // create a nice trace of interpolated variables like so:
0253: // var1->var2->var3
0254: for (Iterator it = priorVariables.iterator(); it
0255: .hasNext();) {
0256: priorVariableSb.append(it.next());
0257: if (it.hasNext()) {
0258: priorVariableSb.append("->");
0259: }
0260: }
0261:
0262: throw new IllegalStateException(
0263: "infinite loop in property interpolation of "
0264: + initialBase + ": "
0265: + priorVariableSb.toString());
0266: }
0267: // otherwise, add this variable to the interpolation list.
0268: else {
0269: priorVariables.add(variable);
0270: }
0271:
0272: //QUESTION: getProperty or getPropertyDirect
0273: Object value = getProperty(variable);
0274: if (value != null) {
0275: result.append(interpolateHelper(value.toString(),
0276: priorVariables));
0277:
0278: // pop the interpolated variable off the stack
0279: // this maintains priorVariables correctness for
0280: // properties with multiple interpolations, e.g.
0281: // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
0282: priorVariables.remove(priorVariables.size() - 1);
0283: } else if (defaults != null
0284: && defaults.getString(variable, null) != null) {
0285: result.append(defaults.getString(variable));
0286: } else {
0287: //variable not defined - so put it back in the value
0288: result.append(START_TOKEN).append(variable).append(
0289: END_TOKEN);
0290: }
0291: prec = end;
0292: }
0293: result.append(base.substring(prec + END_TOKEN.length(), base
0294: .length()));
0295:
0296: return result.toString();
0297: }
0298:
0299: /**
0300: * Inserts a backslash before every comma and backslash.
0301: */
0302: private static String escape(String s) {
0303: StringBuffer buf = new StringBuffer(s);
0304: for (int i = 0; i < buf.length(); i++) {
0305: char c = buf.charAt(i);
0306: if (c == ',' || c == '\\') {
0307: buf.insert(i, '\\');
0308: i++;
0309: }
0310: }
0311: return buf.toString();
0312: }
0313:
0314: /**
0315: * Removes a backslash from every pair of backslashes.
0316: */
0317: private static String unescape(String s) {
0318: StringBuffer buf = new StringBuffer(s);
0319: for (int i = 0; i < buf.length() - 1; i++) {
0320: char c1 = buf.charAt(i);
0321: char c2 = buf.charAt(i + 1);
0322: if (c1 == '\\' && c2 == '\\') {
0323: buf.deleteCharAt(i);
0324: }
0325: }
0326: return buf.toString();
0327: }
0328:
0329: /**
0330: * Counts the number of successive times 'ch' appears in the
0331: * 'line' before the position indicated by the 'index'.
0332: */
0333: private static int countPreceding(String line, int index, char ch) {
0334: int i;
0335: for (i = index - 1; i >= 0; i--) {
0336: if (line.charAt(i) != ch) {
0337: break;
0338: }
0339: }
0340: return index - 1 - i;
0341: }
0342:
0343: /**
0344: * Checks if the line ends with odd number of backslashes
0345: */
0346: private static boolean endsWithSlash(String line) {
0347: if (!line.endsWith("\\")) {
0348: return false;
0349: }
0350: return (countPreceding(line, line.length() - 1, '\\') % 2 == 0);
0351: }
0352:
0353: /**
0354: * This class is used to read properties lines. These lines do
0355: * not terminate with new-line chars but rather when there is no
0356: * backslash sign a the end of the line. This is used to
0357: * concatenate multiple lines for readability.
0358: */
0359: static class PropertiesReader extends LineNumberReader {
0360: /**
0361: * Constructor.
0362: *
0363: * @param reader A Reader.
0364: */
0365: public PropertiesReader(Reader reader) {
0366: super (reader);
0367: }
0368:
0369: /**
0370: * Read a property.
0371: *
0372: * @return a String property
0373: * @throws IOException if there is difficulty reading the source.
0374: */
0375: public String readProperty() throws IOException {
0376: StringBuffer buffer = new StringBuffer();
0377: String line = readLine();
0378: while (line != null) {
0379: line = line.trim();
0380: if ((line.length() != 0) && (line.charAt(0) != '#')) {
0381: if (endsWithSlash(line)) {
0382: line = line.substring(0, line.length() - 1);
0383: buffer.append(line);
0384: } else {
0385: buffer.append(line);
0386: return buffer.toString(); // normal method end
0387: }
0388: }
0389: line = readLine();
0390: }
0391: return null; // EOF reached
0392: }
0393: }
0394:
0395: /**
0396: * This class divides into tokens a property value. Token
0397: * separator is "," but commas into the property value are escaped
0398: * using the backslash in front.
0399: */
0400: static class PropertiesTokenizer extends StringTokenizer {
0401: /**
0402: * The property delimiter used while parsing (a comma).
0403: */
0404: static final String DELIMITER = ",";
0405:
0406: /**
0407: * Constructor.
0408: *
0409: * @param string A String.
0410: */
0411: public PropertiesTokenizer(String string) {
0412: super (string, DELIMITER);
0413: }
0414:
0415: /**
0416: * Check whether the object has more tokens.
0417: *
0418: * @return True if the object has more tokens.
0419: */
0420: public boolean hasMoreTokens() {
0421: return super .hasMoreTokens();
0422: }
0423:
0424: /**
0425: * Get next token.
0426: *
0427: * @return A String.
0428: */
0429: public String nextToken() {
0430: StringBuffer buffer = new StringBuffer();
0431:
0432: while (hasMoreTokens()) {
0433: String token = super .nextToken();
0434: if (endsWithSlash(token)) {
0435: buffer.append(token
0436: .substring(0, token.length() - 1));
0437: buffer.append(DELIMITER);
0438: } else {
0439: buffer.append(token);
0440: break;
0441: }
0442: }
0443:
0444: return buffer.toString().trim();
0445: }
0446: }
0447:
0448: /**
0449: * Creates an empty extended properties object.
0450: */
0451: public ExtendedProperties() {
0452: super ();
0453: }
0454:
0455: /**
0456: * Creates and loads the extended properties from the specified file.
0457: *
0458: * @param file the filename to load
0459: * @throws IOException if a file error occurs
0460: */
0461: public ExtendedProperties(String file) throws IOException {
0462: this (file, null);
0463: }
0464:
0465: /**
0466: * Creates and loads the extended properties from the specified file.
0467: *
0468: * @param file the filename to load
0469: * @param defaultFile a second filename to load default values from
0470: * @throws IOException if a file error occurs
0471: */
0472: public ExtendedProperties(String file, String defaultFile)
0473: throws IOException {
0474: this .file = file;
0475:
0476: basePath = new File(file).getAbsolutePath();
0477: basePath = basePath.substring(0, basePath
0478: .lastIndexOf(fileSeparator) + 1);
0479:
0480: FileInputStream in = null;
0481: try {
0482: in = new FileInputStream(file);
0483: this .load(in);
0484: } finally {
0485: try {
0486: if (in != null) {
0487: in.close();
0488: }
0489: } catch (IOException ex) {
0490: }
0491: }
0492:
0493: if (defaultFile != null) {
0494: defaults = new ExtendedProperties(defaultFile);
0495: }
0496: }
0497:
0498: /**
0499: * Indicate to client code whether property
0500: * resources have been initialized or not.
0501: */
0502: public boolean isInitialized() {
0503: return isInitialized;
0504: }
0505:
0506: /**
0507: * Gets the property value for including other properties files.
0508: * By default it is "include".
0509: *
0510: * @return A String.
0511: */
0512: public String getInclude() {
0513: return include;
0514: }
0515:
0516: /**
0517: * Sets the property value for including other properties files.
0518: * By default it is "include".
0519: *
0520: * @param inc A String.
0521: */
0522: public void setInclude(String inc) {
0523: include = inc;
0524: }
0525:
0526: /**
0527: * Load the properties from the given input stream.
0528: *
0529: * @param input the InputStream to load from
0530: * @throws IOException if an IO error occurs
0531: */
0532: public void load(InputStream input) throws IOException {
0533: load(input, null);
0534: }
0535:
0536: /**
0537: * Load the properties from the given input stream
0538: * and using the specified encoding.
0539: *
0540: * @param input the InputStream to load from
0541: * @param enc the encoding to use
0542: * @throws IOException if an IO error occurs
0543: */
0544: public synchronized void load(InputStream input, String enc)
0545: throws IOException {
0546: PropertiesReader reader = null;
0547: if (enc != null) {
0548: try {
0549: reader = new PropertiesReader(new InputStreamReader(
0550: input, enc));
0551:
0552: } catch (UnsupportedEncodingException ex) {
0553: // Another try coming up....
0554: }
0555: }
0556:
0557: if (reader == null) {
0558: try {
0559: reader = new PropertiesReader(new InputStreamReader(
0560: input, "8859_1"));
0561:
0562: } catch (UnsupportedEncodingException ex) {
0563: // ISO8859-1 support is required on java platforms but....
0564: // If it's not supported, use the system default encoding
0565: reader = new PropertiesReader(new InputStreamReader(
0566: input));
0567: }
0568: }
0569:
0570: try {
0571: while (true) {
0572: String line = reader.readProperty();
0573: if (line == null) {
0574: return; // EOF
0575: }
0576: int equalSign = line.indexOf('=');
0577:
0578: if (equalSign > 0) {
0579: String key = line.substring(0, equalSign).trim();
0580: String value = line.substring(equalSign + 1).trim();
0581:
0582: // Configure produces lines like this ... just ignore them
0583: if ("".equals(value)) {
0584: continue;
0585: }
0586:
0587: if (getInclude() != null
0588: && key.equalsIgnoreCase(getInclude())) {
0589: // Recursively load properties files.
0590: File file = null;
0591:
0592: if (value.startsWith(fileSeparator)) {
0593: // We have an absolute path so we'll use this
0594: file = new File(value);
0595:
0596: } else {
0597: // We have a relative path, and we have two
0598: // possible forms here. If we have the "./" form
0599: // then just strip that off first before continuing.
0600: if (value.startsWith("." + fileSeparator)) {
0601: value = value.substring(2);
0602: }
0603:
0604: file = new File(basePath + value);
0605: }
0606:
0607: if (file != null && file.exists()
0608: && file.canRead()) {
0609: load(new FileInputStream(file));
0610: }
0611: } else {
0612: addProperty(key, value);
0613: }
0614: }
0615: }
0616: } finally {
0617: // Loading is initializing
0618: isInitialized = true;
0619: }
0620: }
0621:
0622: /**
0623: * Gets a property from the configuration.
0624: *
0625: * @param key property to retrieve
0626: * @return value as object. Will return user value if exists,
0627: * if not then default value if exists, otherwise null
0628: */
0629: public Object getProperty(String key) {
0630: // first, try to get from the 'user value' store
0631: Object obj = this .get(key);
0632:
0633: if (obj == null) {
0634: // if there isn't a value there, get it from the
0635: // defaults if we have them
0636: if (defaults != null) {
0637: obj = defaults.get(key);
0638: }
0639: }
0640:
0641: return obj;
0642: }
0643:
0644: /**
0645: * Add a property to the configuration. If it already
0646: * exists then the value stated here will be added
0647: * to the configuration entry. For example, if
0648: *
0649: * <code>resource.loader = file</code>
0650: *
0651: * is already present in the configuration and you
0652: *
0653: * <code>addProperty("resource.loader", "classpath")</code>
0654: *
0655: * Then you will end up with a Vector like the
0656: * following:
0657: *
0658: * <code>["file", "classpath"]</code>
0659: *
0660: * @param key the key to add
0661: * @param value the value to add
0662: */
0663: public void addProperty(String key, Object value) {
0664: if (value instanceof String) {
0665: String str = (String) value;
0666: if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0) {
0667: // token contains commas, so must be split apart then added
0668: PropertiesTokenizer tokenizer = new PropertiesTokenizer(
0669: str);
0670: while (tokenizer.hasMoreTokens()) {
0671: String token = tokenizer.nextToken();
0672: addPropertyInternal(key, unescape(token));
0673: }
0674: } else {
0675: // token contains no commas, so can be simply added
0676: addPropertyInternal(key, unescape(str));
0677: }
0678: } else {
0679: addPropertyInternal(key, value);
0680: }
0681:
0682: // Adding a property connotes initialization
0683: isInitialized = true;
0684: }
0685:
0686: /**
0687: * Adds a key/value pair to the map. This routine does
0688: * no magic morphing. It ensures the keylist is maintained
0689: *
0690: * @param key the key to store at
0691: * @param value the decoded object to store
0692: */
0693: private void addPropertyDirect(String key, Object value) {
0694: // safety check
0695: if (!containsKey(key)) {
0696: keysAsListed.add(key);
0697: }
0698: put(key, value);
0699: }
0700:
0701: /**
0702: * Adds a decoded property to the map w/o checking for commas - used
0703: * internally when a property has been broken up into
0704: * strings that could contain escaped commas to prevent
0705: * the inadvertent vectorization.
0706: * <p>
0707: * Thanks to Leon Messerschmidt for this one.
0708: *
0709: * @param key the key to store at
0710: * @param value the decoded object to store
0711: */
0712: private void addPropertyInternal(String key, Object value) {
0713: Object current = this .get(key);
0714:
0715: if (current instanceof String) {
0716: // one object already in map - convert it to a vector
0717: List values = new Vector(2);
0718: values.add(current);
0719: values.add(value);
0720: put(key, values);
0721:
0722: } else if (current instanceof List) {
0723: // already a list - just add the new token
0724: ((List) current).add(value);
0725:
0726: } else {
0727: // brand new key - store in keysAsListed to retain order
0728: if (!containsKey(key)) {
0729: keysAsListed.add(key);
0730: }
0731: put(key, value);
0732: }
0733: }
0734:
0735: /**
0736: * Set a property, this will replace any previously
0737: * set values. Set values is implicitly a call
0738: * to clearProperty(key), addProperty(key,value).
0739: *
0740: * @param key the key to set
0741: * @param value the value to set
0742: */
0743: public void setProperty(String key, Object value) {
0744: clearProperty(key);
0745: addProperty(key, value);
0746: }
0747:
0748: /**
0749: * Save the properties to the given output stream.
0750: * <p>
0751: * The stream is not closed, but it is flushed.
0752: *
0753: * @param output an OutputStream, may be null
0754: * @param header a textual comment to act as a file header
0755: * @throws IOException if an IO error occurs
0756: */
0757: public synchronized void save(OutputStream output, String header)
0758: throws IOException {
0759: if (output == null) {
0760: return;
0761: }
0762: PrintWriter theWrtr = new PrintWriter(output);
0763: if (header != null) {
0764: theWrtr.println(header);
0765: }
0766:
0767: Enumeration theKeys = keys();
0768: while (theKeys.hasMoreElements()) {
0769: String key = (String) theKeys.nextElement();
0770: Object value = get(key);
0771: if (value != null) {
0772: if (value instanceof String) {
0773: StringBuffer currentOutput = new StringBuffer();
0774: currentOutput.append(key);
0775: currentOutput.append("=");
0776: currentOutput.append(escape((String) value));
0777: theWrtr.println(currentOutput.toString());
0778:
0779: } else if (value instanceof List) {
0780: List values = (List) value;
0781: for (Iterator it = values.iterator(); it.hasNext();) {
0782: String currentElement = (String) it.next();
0783: StringBuffer currentOutput = new StringBuffer();
0784: currentOutput.append(key);
0785: currentOutput.append("=");
0786: currentOutput.append(escape(currentElement));
0787: theWrtr.println(currentOutput.toString());
0788: }
0789: }
0790: }
0791: theWrtr.println();
0792: theWrtr.flush();
0793: }
0794: }
0795:
0796: /**
0797: * Combines an existing Hashtable with this Hashtable.
0798: * <p>
0799: * Warning: It will overwrite previous entries without warning.
0800: *
0801: * @param props the properties to combine
0802: */
0803: public void combine(ExtendedProperties props) {
0804: for (Iterator it = props.getKeys(); it.hasNext();) {
0805: String key = (String) it.next();
0806: setProperty(key, props.get(key));
0807: }
0808: }
0809:
0810: /**
0811: * Clear a property in the configuration.
0812: *
0813: * @param key the property key to remove along with corresponding value
0814: */
0815: public void clearProperty(String key) {
0816: if (containsKey(key)) {
0817: // we also need to rebuild the keysAsListed or else
0818: // things get *very* confusing
0819: for (int i = 0; i < keysAsListed.size(); i++) {
0820: if ((keysAsListed.get(i)).equals(key)) {
0821: keysAsListed.remove(i);
0822: break;
0823: }
0824: }
0825: remove(key);
0826: }
0827: }
0828:
0829: /**
0830: * Get the list of the keys contained in the configuration
0831: * repository.
0832: *
0833: * @return an Iterator over the keys
0834: */
0835: public Iterator getKeys() {
0836: return keysAsListed.iterator();
0837: }
0838:
0839: /**
0840: * Get the list of the keys contained in the configuration
0841: * repository that match the specified prefix.
0842: *
0843: * @param prefix the prefix to match
0844: * @return an Iterator of keys that match the prefix
0845: */
0846: public Iterator getKeys(String prefix) {
0847: Iterator keys = getKeys();
0848: ArrayList matchingKeys = new ArrayList();
0849:
0850: while (keys.hasNext()) {
0851: Object key = keys.next();
0852:
0853: if (key instanceof String
0854: && ((String) key).startsWith(prefix)) {
0855: matchingKeys.add(key);
0856: }
0857: }
0858: return matchingKeys.iterator();
0859: }
0860:
0861: /**
0862: * Create an ExtendedProperties object that is a subset
0863: * of this one. Take into account duplicate keys
0864: * by using the setProperty() in ExtendedProperties.
0865: *
0866: * @param prefix the prefix to get a subset for
0867: * @return a new independent ExtendedProperties
0868: */
0869: public ExtendedProperties subset(String prefix) {
0870: ExtendedProperties c = new ExtendedProperties();
0871: Iterator keys = getKeys();
0872: boolean validSubset = false;
0873:
0874: while (keys.hasNext()) {
0875: Object key = keys.next();
0876:
0877: if (key instanceof String
0878: && ((String) key).startsWith(prefix)) {
0879: if (!validSubset) {
0880: validSubset = true;
0881: }
0882:
0883: /*
0884: * Check to make sure that c.subset(prefix) doesn't
0885: * blow up when there is only a single property
0886: * with the key prefix. This is not a useful
0887: * subset but it is a valid subset.
0888: */
0889: String newKey = null;
0890: if (((String) key).length() == prefix.length()) {
0891: newKey = prefix;
0892: } else {
0893: newKey = ((String) key)
0894: .substring(prefix.length() + 1);
0895: }
0896:
0897: /*
0898: * use addPropertyDirect() - this will plug the data as
0899: * is into the Map, but will also do the right thing
0900: * re key accounting
0901: */
0902: c.addPropertyDirect(newKey, get(key));
0903: }
0904: }
0905:
0906: if (validSubset) {
0907: return c;
0908: } else {
0909: return null;
0910: }
0911: }
0912:
0913: /**
0914: * Display the configuration for debugging purposes to System.out.
0915: */
0916: public void display() {
0917: Iterator i = getKeys();
0918:
0919: while (i.hasNext()) {
0920: String key = (String) i.next();
0921: Object value = get(key);
0922: System.out.println(key + " => " + value);
0923: }
0924: }
0925:
0926: /**
0927: * Get a string associated with the given configuration key.
0928: *
0929: * @param key The configuration key.
0930: * @return The associated string.
0931: * @throws ClassCastException is thrown if the key maps to an
0932: * object that is not a String.
0933: */
0934: public String getString(String key) {
0935: return getString(key, null);
0936: }
0937:
0938: /**
0939: * Get a string associated with the given configuration key.
0940: *
0941: * @param key The configuration key.
0942: * @param defaultValue The default value.
0943: * @return The associated string if key is found,
0944: * default value otherwise.
0945: * @throws ClassCastException is thrown if the key maps to an
0946: * object that is not a String.
0947: */
0948: public String getString(String key, String defaultValue) {
0949: Object value = get(key);
0950:
0951: if (value instanceof String) {
0952: return interpolate((String) value);
0953:
0954: } else if (value == null) {
0955: if (defaults != null) {
0956: return interpolate(defaults
0957: .getString(key, defaultValue));
0958: } else {
0959: return interpolate(defaultValue);
0960: }
0961: } else if (value instanceof List) {
0962: return interpolate((String) ((List) value).get(0));
0963: } else {
0964: throw new ClassCastException('\'' + key
0965: + "' doesn't map to a String object");
0966: }
0967: }
0968:
0969: /**
0970: * Get a list of properties associated with the given
0971: * configuration key.
0972: *
0973: * @param key The configuration key.
0974: * @return The associated properties if key is found.
0975: * @throws ClassCastException is thrown if the key maps to an
0976: * object that is not a String/List.
0977: * @throws IllegalArgumentException if one of the tokens is
0978: * malformed (does not contain an equals sign).
0979: */
0980: public Properties getProperties(String key) {
0981: return getProperties(key, new Properties());
0982: }
0983:
0984: /**
0985: * Get a list of properties associated with the given
0986: * configuration key.
0987: *
0988: * @param key The configuration key.
0989: * @return The associated properties if key is found.
0990: * @throws ClassCastException is thrown if the key maps to an
0991: * object that is not a String/List.
0992: * @throws IllegalArgumentException if one of the tokens is
0993: * malformed (does not contain an equals sign).
0994: */
0995: public Properties getProperties(String key, Properties defaults) {
0996: /*
0997: * Grab an array of the tokens for this key.
0998: */
0999: String[] tokens = getStringArray(key);
1000:
1001: // Each token is of the form 'key=value'.
1002: Properties props = new Properties(defaults);
1003: for (int i = 0; i < tokens.length; i++) {
1004: String token = tokens[i];
1005: int equalSign = token.indexOf('=');
1006: if (equalSign > 0) {
1007: String pkey = token.substring(0, equalSign).trim();
1008: String pvalue = token.substring(equalSign + 1).trim();
1009: props.put(pkey, pvalue);
1010: } else {
1011: throw new IllegalArgumentException('\'' + token
1012: + "' does not contain " + "an equals sign");
1013: }
1014: }
1015: return props;
1016: }
1017:
1018: /**
1019: * Get an array of strings associated with the given configuration
1020: * key.
1021: *
1022: * @param key The configuration key.
1023: * @return The associated string array if key is found.
1024: * @throws ClassCastException is thrown if the key maps to an
1025: * object that is not a String/List.
1026: */
1027: public String[] getStringArray(String key) {
1028: Object value = get(key);
1029:
1030: List values;
1031: if (value instanceof String) {
1032: values = new Vector(1);
1033: values.add(value);
1034:
1035: } else if (value instanceof List) {
1036: values = (List) value;
1037:
1038: } else if (value == null) {
1039: if (defaults != null) {
1040: return defaults.getStringArray(key);
1041: } else {
1042: return new String[0];
1043: }
1044: } else {
1045: throw new ClassCastException('\'' + key
1046: + "' doesn't map to a String/List object");
1047: }
1048:
1049: String[] tokens = new String[values.size()];
1050: for (int i = 0; i < tokens.length; i++) {
1051: tokens[i] = (String) values.get(i);
1052: }
1053:
1054: return tokens;
1055: }
1056:
1057: /**
1058: * Get a Vector of strings associated with the given configuration
1059: * key.
1060: *
1061: * @param key The configuration key.
1062: * @return The associated Vector.
1063: * @throws ClassCastException is thrown if the key maps to an
1064: * object that is not a Vector.
1065: */
1066: public Vector getVector(String key) {
1067: return getVector(key, null);
1068: }
1069:
1070: /**
1071: * Get a Vector of strings associated with the given configuration key.
1072: * <p>
1073: * The list is a copy of the internal data of this object, and as
1074: * such you may alter it freely.
1075: *
1076: * @param key The configuration key.
1077: * @param defaultValue The default value.
1078: * @return The associated Vector.
1079: * @throws ClassCastException is thrown if the key maps to an
1080: * object that is not a Vector.
1081: */
1082: public Vector getVector(String key, Vector defaultValue) {
1083: Object value = get(key);
1084:
1085: if (value instanceof List) {
1086: return new Vector((List) value);
1087:
1088: } else if (value instanceof String) {
1089: Vector values = new Vector(1);
1090: values.add(value);
1091: put(key, values);
1092: return values;
1093:
1094: } else if (value == null) {
1095: if (defaults != null) {
1096: return defaults.getVector(key, defaultValue);
1097: } else {
1098: return ((defaultValue == null) ? new Vector()
1099: : defaultValue);
1100: }
1101: } else {
1102: throw new ClassCastException('\'' + key
1103: + "' doesn't map to a Vector object");
1104: }
1105: }
1106:
1107: /**
1108: * Get a List of strings associated with the given configuration key.
1109: * <p>
1110: * The list is a copy of the internal data of this object, and as
1111: * such you may alter it freely.
1112: *
1113: * @param key The configuration key.
1114: * @return The associated List object.
1115: * @throws ClassCastException is thrown if the key maps to an
1116: * object that is not a List.
1117: * @since Commons Collections 3.2
1118: */
1119: public List getList(String key) {
1120: return getList(key, null);
1121: }
1122:
1123: /**
1124: * Get a List of strings associated with the given configuration key.
1125: * <p>
1126: * The list is a copy of the internal data of this object, and as
1127: * such you may alter it freely.
1128: *
1129: * @param key The configuration key.
1130: * @param defaultValue The default value.
1131: * @return The associated List.
1132: * @throws ClassCastException is thrown if the key maps to an
1133: * object that is not a List.
1134: * @since Commons Collections 3.2
1135: */
1136: public List getList(String key, List defaultValue) {
1137: Object value = get(key);
1138:
1139: if (value instanceof List) {
1140: return new ArrayList((List) value);
1141:
1142: } else if (value instanceof String) {
1143: List values = new ArrayList(1);
1144: values.add(value);
1145: put(key, values);
1146: return values;
1147:
1148: } else if (value == null) {
1149: if (defaults != null) {
1150: return defaults.getList(key, defaultValue);
1151: } else {
1152: return ((defaultValue == null) ? new ArrayList()
1153: : defaultValue);
1154: }
1155: } else {
1156: throw new ClassCastException('\'' + key
1157: + "' doesn't map to a List object");
1158: }
1159: }
1160:
1161: /**
1162: * Get a boolean associated with the given configuration key.
1163: *
1164: * @param key The configuration key.
1165: * @return The associated boolean.
1166: * @throws NoSuchElementException is thrown if the key doesn't
1167: * map to an existing object.
1168: * @throws ClassCastException is thrown if the key maps to an
1169: * object that is not a Boolean.
1170: */
1171: public boolean getBoolean(String key) {
1172: Boolean b = getBoolean(key, null);
1173: if (b != null) {
1174: return b.booleanValue();
1175: } else {
1176: throw new NoSuchElementException('\'' + key
1177: + "' doesn't map to an existing object");
1178: }
1179: }
1180:
1181: /**
1182: * Get a boolean associated with the given configuration key.
1183: *
1184: * @param key The configuration key.
1185: * @param defaultValue The default value.
1186: * @return The associated boolean.
1187: * @throws ClassCastException is thrown if the key maps to an
1188: * object that is not a Boolean.
1189: */
1190: public boolean getBoolean(String key, boolean defaultValue) {
1191: return getBoolean(key, new Boolean(defaultValue))
1192: .booleanValue();
1193: }
1194:
1195: /**
1196: * Get a boolean associated with the given configuration key.
1197: *
1198: * @param key The configuration key.
1199: * @param defaultValue The default value.
1200: * @return The associated boolean if key is found and has valid
1201: * format, default value otherwise.
1202: * @throws ClassCastException is thrown if the key maps to an
1203: * object that is not a Boolean.
1204: */
1205: public Boolean getBoolean(String key, Boolean defaultValue) {
1206:
1207: Object value = get(key);
1208:
1209: if (value instanceof Boolean) {
1210: return (Boolean) value;
1211:
1212: } else if (value instanceof String) {
1213: String s = testBoolean((String) value);
1214: Boolean b = new Boolean(s);
1215: put(key, b);
1216: return b;
1217:
1218: } else if (value == null) {
1219: if (defaults != null) {
1220: return defaults.getBoolean(key, defaultValue);
1221: } else {
1222: return defaultValue;
1223: }
1224: } else {
1225: throw new ClassCastException('\'' + key
1226: + "' doesn't map to a Boolean object");
1227: }
1228: }
1229:
1230: /**
1231: * Test whether the string represent by value maps to a boolean
1232: * value or not. We will allow <code>true</code>, <code>on</code>,
1233: * and <code>yes</code> for a <code>true</code> boolean value, and
1234: * <code>false</code>, <code>off</code>, and <code>no</code> for
1235: * <code>false</code> boolean values. Case of value to test for
1236: * boolean status is ignored.
1237: *
1238: * @param value the value to test for boolean state
1239: * @return <code>true</code> or <code>false</code> if the supplied
1240: * text maps to a boolean value, or <code>null</code> otherwise.
1241: */
1242: public String testBoolean(String value) {
1243: String s = value.toLowerCase();
1244:
1245: if (s.equals("true") || s.equals("on") || s.equals("yes")) {
1246: return "true";
1247: } else if (s.equals("false") || s.equals("off")
1248: || s.equals("no")) {
1249: return "false";
1250: } else {
1251: return null;
1252: }
1253: }
1254:
1255: /**
1256: * Get a byte associated with the given configuration key.
1257: *
1258: * @param key The configuration key.
1259: * @return The associated byte.
1260: * @throws NoSuchElementException is thrown if the key doesn't
1261: * map to an existing object.
1262: * @throws ClassCastException is thrown if the key maps to an
1263: * object that is not a Byte.
1264: * @throws NumberFormatException is thrown if the value mapped
1265: * by the key has not a valid number format.
1266: */
1267: public byte getByte(String key) {
1268: Byte b = getByte(key, null);
1269: if (b != null) {
1270: return b.byteValue();
1271: } else {
1272: throw new NoSuchElementException('\'' + key
1273: + " doesn't map to an existing object");
1274: }
1275: }
1276:
1277: /**
1278: * Get a byte associated with the given configuration key.
1279: *
1280: * @param key The configuration key.
1281: * @param defaultValue The default value.
1282: * @return The associated byte.
1283: * @throws ClassCastException is thrown if the key maps to an
1284: * object that is not a Byte.
1285: * @throws NumberFormatException is thrown if the value mapped
1286: * by the key has not a valid number format.
1287: */
1288: public byte getByte(String key, byte defaultValue) {
1289: return getByte(key, new Byte(defaultValue)).byteValue();
1290: }
1291:
1292: /**
1293: * Get a byte associated with the given configuration key.
1294: *
1295: * @param key The configuration key.
1296: * @param defaultValue The default value.
1297: * @return The associated byte if key is found and has valid
1298: * format, default value otherwise.
1299: * @throws ClassCastException is thrown if the key maps to an
1300: * object that is not a Byte.
1301: * @throws NumberFormatException is thrown if the value mapped
1302: * by the key has not a valid number format.
1303: */
1304: public Byte getByte(String key, Byte defaultValue) {
1305: Object value = get(key);
1306:
1307: if (value instanceof Byte) {
1308: return (Byte) value;
1309:
1310: } else if (value instanceof String) {
1311: Byte b = new Byte((String) value);
1312: put(key, b);
1313: return b;
1314:
1315: } else if (value == null) {
1316: if (defaults != null) {
1317: return defaults.getByte(key, defaultValue);
1318: } else {
1319: return defaultValue;
1320: }
1321: } else {
1322: throw new ClassCastException('\'' + key
1323: + "' doesn't map to a Byte object");
1324: }
1325: }
1326:
1327: /**
1328: * Get a short associated with the given configuration key.
1329: *
1330: * @param key The configuration key.
1331: * @return The associated short.
1332: * @throws NoSuchElementException is thrown if the key doesn't
1333: * map to an existing object.
1334: * @throws ClassCastException is thrown if the key maps to an
1335: * object that is not a Short.
1336: * @throws NumberFormatException is thrown if the value mapped
1337: * by the key has not a valid number format.
1338: */
1339: public short getShort(String key) {
1340: Short s = getShort(key, null);
1341: if (s != null) {
1342: return s.shortValue();
1343: } else {
1344: throw new NoSuchElementException('\'' + key
1345: + "' doesn't map to an existing object");
1346: }
1347: }
1348:
1349: /**
1350: * Get a short associated with the given configuration key.
1351: *
1352: * @param key The configuration key.
1353: * @param defaultValue The default value.
1354: * @return The associated short.
1355: * @throws ClassCastException is thrown if the key maps to an
1356: * object that is not a Short.
1357: * @throws NumberFormatException is thrown if the value mapped
1358: * by the key has not a valid number format.
1359: */
1360: public short getShort(String key, short defaultValue) {
1361: return getShort(key, new Short(defaultValue)).shortValue();
1362: }
1363:
1364: /**
1365: * Get a short associated with the given configuration key.
1366: *
1367: * @param key The configuration key.
1368: * @param defaultValue The default value.
1369: * @return The associated short if key is found and has valid
1370: * format, default value otherwise.
1371: * @throws ClassCastException is thrown if the key maps to an
1372: * object that is not a Short.
1373: * @throws NumberFormatException is thrown if the value mapped
1374: * by the key has not a valid number format.
1375: */
1376: public Short getShort(String key, Short defaultValue) {
1377: Object value = get(key);
1378:
1379: if (value instanceof Short) {
1380: return (Short) value;
1381:
1382: } else if (value instanceof String) {
1383: Short s = new Short((String) value);
1384: put(key, s);
1385: return s;
1386:
1387: } else if (value == null) {
1388: if (defaults != null) {
1389: return defaults.getShort(key, defaultValue);
1390: } else {
1391: return defaultValue;
1392: }
1393: } else {
1394: throw new ClassCastException('\'' + key
1395: + "' doesn't map to a Short object");
1396: }
1397: }
1398:
1399: /**
1400: * The purpose of this method is to get the configuration resource
1401: * with the given name as an integer.
1402: *
1403: * @param name The resource name.
1404: * @return The value of the resource as an integer.
1405: */
1406: public int getInt(String name) {
1407: return getInteger(name);
1408: }
1409:
1410: /**
1411: * The purpose of this method is to get the configuration resource
1412: * with the given name as an integer, or a default value.
1413: *
1414: * @param name The resource name
1415: * @param def The default value of the resource.
1416: * @return The value of the resource as an integer.
1417: */
1418: public int getInt(String name, int def) {
1419: return getInteger(name, def);
1420: }
1421:
1422: /**
1423: * Get a int associated with the given configuration key.
1424: *
1425: * @param key The configuration key.
1426: * @return The associated int.
1427: * @throws NoSuchElementException is thrown if the key doesn't
1428: * map to an existing object.
1429: * @throws ClassCastException is thrown if the key maps to an
1430: * object that is not a Integer.
1431: * @throws NumberFormatException is thrown if the value mapped
1432: * by the key has not a valid number format.
1433: */
1434: public int getInteger(String key) {
1435: Integer i = getInteger(key, null);
1436: if (i != null) {
1437: return i.intValue();
1438: } else {
1439: throw new NoSuchElementException('\'' + key
1440: + "' doesn't map to an existing object");
1441: }
1442: }
1443:
1444: /**
1445: * Get a int associated with the given configuration key.
1446: *
1447: * @param key The configuration key.
1448: * @param defaultValue The default value.
1449: * @return The associated int.
1450: * @throws ClassCastException is thrown if the key maps to an
1451: * object that is not a Integer.
1452: * @throws NumberFormatException is thrown if the value mapped
1453: * by the key has not a valid number format.
1454: */
1455: public int getInteger(String key, int defaultValue) {
1456: Integer i = getInteger(key, null);
1457:
1458: if (i == null) {
1459: return defaultValue;
1460: }
1461: return i.intValue();
1462: }
1463:
1464: /**
1465: * Get a int associated with the given configuration key.
1466: *
1467: * @param key The configuration key.
1468: * @param defaultValue The default value.
1469: * @return The associated int if key is found and has valid
1470: * format, default value otherwise.
1471: * @throws ClassCastException is thrown if the key maps to an
1472: * object that is not a Integer.
1473: * @throws NumberFormatException is thrown if the value mapped
1474: * by the key has not a valid number format.
1475: */
1476: public Integer getInteger(String key, Integer defaultValue) {
1477: Object value = get(key);
1478:
1479: if (value instanceof Integer) {
1480: return (Integer) value;
1481:
1482: } else if (value instanceof String) {
1483: Integer i = new Integer((String) value);
1484: put(key, i);
1485: return i;
1486:
1487: } else if (value == null) {
1488: if (defaults != null) {
1489: return defaults.getInteger(key, defaultValue);
1490: } else {
1491: return defaultValue;
1492: }
1493: } else {
1494: throw new ClassCastException('\'' + key
1495: + "' doesn't map to a Integer object");
1496: }
1497: }
1498:
1499: /**
1500: * Get a long associated with the given configuration key.
1501: *
1502: * @param key The configuration key.
1503: * @return The associated long.
1504: * @throws NoSuchElementException is thrown if the key doesn't
1505: * map to an existing object.
1506: * @throws ClassCastException is thrown if the key maps to an
1507: * object that is not a Long.
1508: * @throws NumberFormatException is thrown if the value mapped
1509: * by the key has not a valid number format.
1510: */
1511: public long getLong(String key) {
1512: Long l = getLong(key, null);
1513: if (l != null) {
1514: return l.longValue();
1515: } else {
1516: throw new NoSuchElementException('\'' + key
1517: + "' doesn't map to an existing object");
1518: }
1519: }
1520:
1521: /**
1522: * Get a long associated with the given configuration key.
1523: *
1524: * @param key The configuration key.
1525: * @param defaultValue The default value.
1526: * @return The associated long.
1527: * @throws ClassCastException is thrown if the key maps to an
1528: * object that is not a Long.
1529: * @throws NumberFormatException is thrown if the value mapped
1530: * by the key has not a valid number format.
1531: */
1532: public long getLong(String key, long defaultValue) {
1533: return getLong(key, new Long(defaultValue)).longValue();
1534: }
1535:
1536: /**
1537: * Get a long associated with the given configuration key.
1538: *
1539: * @param key The configuration key.
1540: * @param defaultValue The default value.
1541: * @return The associated long if key is found and has valid
1542: * format, default value otherwise.
1543: * @throws ClassCastException is thrown if the key maps to an
1544: * object that is not a Long.
1545: * @throws NumberFormatException is thrown if the value mapped
1546: * by the key has not a valid number format.
1547: */
1548: public Long getLong(String key, Long defaultValue) {
1549: Object value = get(key);
1550:
1551: if (value instanceof Long) {
1552: return (Long) value;
1553:
1554: } else if (value instanceof String) {
1555: Long l = new Long((String) value);
1556: put(key, l);
1557: return l;
1558:
1559: } else if (value == null) {
1560: if (defaults != null) {
1561: return defaults.getLong(key, defaultValue);
1562: } else {
1563: return defaultValue;
1564: }
1565: } else {
1566: throw new ClassCastException('\'' + key
1567: + "' doesn't map to a Long object");
1568: }
1569: }
1570:
1571: /**
1572: * Get a float associated with the given configuration key.
1573: *
1574: * @param key The configuration key.
1575: * @return The associated float.
1576: * @throws NoSuchElementException is thrown if the key doesn't
1577: * map to an existing object.
1578: * @throws ClassCastException is thrown if the key maps to an
1579: * object that is not a Float.
1580: * @throws NumberFormatException is thrown if the value mapped
1581: * by the key has not a valid number format.
1582: */
1583: public float getFloat(String key) {
1584: Float f = getFloat(key, null);
1585: if (f != null) {
1586: return f.floatValue();
1587: } else {
1588: throw new NoSuchElementException('\'' + key
1589: + "' doesn't map to an existing object");
1590: }
1591: }
1592:
1593: /**
1594: * Get a float associated with the given configuration key.
1595: *
1596: * @param key The configuration key.
1597: * @param defaultValue The default value.
1598: * @return The associated float.
1599: * @throws ClassCastException is thrown if the key maps to an
1600: * object that is not a Float.
1601: * @throws NumberFormatException is thrown if the value mapped
1602: * by the key has not a valid number format.
1603: */
1604: public float getFloat(String key, float defaultValue) {
1605: return getFloat(key, new Float(defaultValue)).floatValue();
1606: }
1607:
1608: /**
1609: * Get a float associated with the given configuration key.
1610: *
1611: * @param key The configuration key.
1612: * @param defaultValue The default value.
1613: * @return The associated float if key is found and has valid
1614: * format, default value otherwise.
1615: * @throws ClassCastException is thrown if the key maps to an
1616: * object that is not a Float.
1617: * @throws NumberFormatException is thrown if the value mapped
1618: * by the key has not a valid number format.
1619: */
1620: public Float getFloat(String key, Float defaultValue) {
1621: Object value = get(key);
1622:
1623: if (value instanceof Float) {
1624: return (Float) value;
1625:
1626: } else if (value instanceof String) {
1627: Float f = new Float((String) value);
1628: put(key, f);
1629: return f;
1630:
1631: } else if (value == null) {
1632: if (defaults != null) {
1633: return defaults.getFloat(key, defaultValue);
1634: } else {
1635: return defaultValue;
1636: }
1637: } else {
1638: throw new ClassCastException('\'' + key
1639: + "' doesn't map to a Float object");
1640: }
1641: }
1642:
1643: /**
1644: * Get a double associated with the given configuration key.
1645: *
1646: * @param key The configuration key.
1647: * @return The associated double.
1648: * @throws NoSuchElementException is thrown if the key doesn't
1649: * map to an existing object.
1650: * @throws ClassCastException is thrown if the key maps to an
1651: * object that is not a Double.
1652: * @throws NumberFormatException is thrown if the value mapped
1653: * by the key has not a valid number format.
1654: */
1655: public double getDouble(String key) {
1656: Double d = getDouble(key, null);
1657: if (d != null) {
1658: return d.doubleValue();
1659: } else {
1660: throw new NoSuchElementException('\'' + key
1661: + "' doesn't map to an existing object");
1662: }
1663: }
1664:
1665: /**
1666: * Get a double associated with the given configuration key.
1667: *
1668: * @param key The configuration key.
1669: * @param defaultValue The default value.
1670: * @return The associated double.
1671: * @throws ClassCastException is thrown if the key maps to an
1672: * object that is not a Double.
1673: * @throws NumberFormatException is thrown if the value mapped
1674: * by the key has not a valid number format.
1675: */
1676: public double getDouble(String key, double defaultValue) {
1677: return getDouble(key, new Double(defaultValue)).doubleValue();
1678: }
1679:
1680: /**
1681: * Get a double associated with the given configuration key.
1682: *
1683: * @param key The configuration key.
1684: * @param defaultValue The default value.
1685: * @return The associated double if key is found and has valid
1686: * format, default value otherwise.
1687: * @throws ClassCastException is thrown if the key maps to an
1688: * object that is not a Double.
1689: * @throws NumberFormatException is thrown if the value mapped
1690: * by the key has not a valid number format.
1691: */
1692: public Double getDouble(String key, Double defaultValue) {
1693: Object value = get(key);
1694:
1695: if (value instanceof Double) {
1696: return (Double) value;
1697:
1698: } else if (value instanceof String) {
1699: Double d = new Double((String) value);
1700: put(key, d);
1701: return d;
1702:
1703: } else if (value == null) {
1704: if (defaults != null) {
1705: return defaults.getDouble(key, defaultValue);
1706: } else {
1707: return defaultValue;
1708: }
1709: } else {
1710: throw new ClassCastException('\'' + key
1711: + "' doesn't map to a Double object");
1712: }
1713: }
1714:
1715: /**
1716: * Convert a standard properties class into a configuration class.
1717: * <p>
1718: * NOTE: From Commons Collections 3.2 this method will pick up
1719: * any default parent Properties of the specified input object.
1720: *
1721: * @param props the properties object to convert
1722: * @return new ExtendedProperties created from props
1723: */
1724: public static ExtendedProperties convertProperties(Properties props) {
1725: ExtendedProperties c = new ExtendedProperties();
1726:
1727: for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
1728: String s = (String) e.nextElement();
1729: c.setProperty(s, props.getProperty(s));
1730: }
1731:
1732: return c;
1733: }
1734:
1735: }
|