0001: package net.myvietnam.mvncore.configuration;
0002:
0003: /* ====================================================================
0004: * The Apache Software License, Version 1.1
0005: *
0006: * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
0007: * reserved.
0008: *
0009: * Redistribution and use in source and binary forms, with or without
0010: * modification, are permitted provided that the following conditions
0011: * are met:
0012: *
0013: * 1. Redistributions of source code must retain the above copyright
0014: * notice, this list of conditions and the following disclaimer.
0015: *
0016: * 2. Redistributions in binary form must reproduce the above copyright
0017: * notice, this list of conditions and the following disclaimer in
0018: * the documentation and/or other materials provided with the
0019: * distribution.
0020: *
0021: * 3. The end-user documentation included with the redistribution, if
0022: * any, must include the following acknowledgement:
0023: * "This product includes software developed by the
0024: * Apache Software Foundation (http://www.apache.org/)."
0025: * Alternately, this acknowledgement may appear in the software itself,
0026: * if and wherever such third-party acknowledgements normally appear.
0027: *
0028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
0029: * Foundation" must not be used to endorse or promote products derived
0030: * from this software without prior written permission. For written
0031: * permission, please contact apache@apache.org.
0032: *
0033: * 5. Products derived from this software may not be called "Apache"
0034: * nor may "Apache" appear in their names without prior written
0035: * permission of the Apache Software Foundation.
0036: *
0037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0048: * SUCH DAMAGE.
0049: * ====================================================================
0050: *
0051: * This software consists of voluntary contributions made by many
0052: * individuals on behalf of the Apache Software Foundation. For more
0053: * information on the Apache Software Foundation, please see
0054: * <http://www.apache.org/>.
0055: */
0056:
0057: import java.util.ArrayList;
0058: import java.util.Collection;
0059: import java.util.Iterator;
0060: import java.util.List;
0061: import java.util.NoSuchElementException;
0062: import java.util.Properties;
0063: import java.util.StringTokenizer;
0064: import java.util.Vector;
0065: import org.apache.commons.logging.Log;
0066: import org.apache.commons.logging.LogFactory;
0067:
0068: /**
0069: * Abstract configuration class. Provide basic functionality but does not
0070: * store any data. If you want to write your own Configuration class
0071: * then you should implement only abstract methods from this class.
0072: *
0073: * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov</a>
0074: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
0075: * @version $Id: AbstractConfiguration.java,v 1.6 2007/10/16 06:54:46 lexuanttkhtn Exp $
0076: */
0077: public abstract class AbstractConfiguration implements Configuration {
0078:
0079: private static Log log = LogFactory
0080: .getLog(AbstractConfiguration.class);
0081:
0082: /** how big the initial arraylist for splitting up name value pairs */
0083: private static final int INITIAL_LIST_SIZE = 2;
0084:
0085: /**
0086: * stores the configuration key-value pairs
0087: */
0088: protected Configuration defaults = null;
0089:
0090: /** start token */
0091: protected static final String START_TOKEN = "${";
0092: /** end token */
0093: protected static final String END_TOKEN = "}";
0094:
0095: /**
0096: * Empty constructor.
0097: */
0098: public AbstractConfiguration() {
0099: }
0100:
0101: /**
0102: * Creates an empty AbstractConfiguration object with
0103: * a Super-Object which is queries for every key.
0104: *
0105: * @param defaults Configuration defaults to use if key not in file
0106: */
0107: public AbstractConfiguration(Configuration defaults) {
0108: this ();
0109: this .defaults = defaults;
0110: }
0111:
0112: /**
0113: * Add a property to the configuration. If it already exists then the value
0114: * stated here will be added to the configuration entry. For example, if
0115: *
0116: * resource.loader = file
0117: *
0118: * is already present in the configuration and you
0119: *
0120: * addProperty("resource.loader", "classpath")
0121: *
0122: * Then you will end up with a Vector like the following:
0123: *
0124: * ["file", "classpath"]
0125: *
0126: * @param key The Key to add the property to.
0127: * @param token The Value to add.
0128: */
0129: public void addProperty(String key, Object token) {
0130: if (token instanceof String) {
0131: for (Iterator it = processString((String) token).iterator(); it
0132: .hasNext();) {
0133: addPropertyDirect(key, it.next());
0134: }
0135: } else if (token instanceof Collection) {
0136: for (Iterator it = ((Collection) token).iterator(); it
0137: .hasNext();) {
0138: addProperty(key, it.next());
0139: }
0140: } else {
0141: addPropertyDirect(key, token);
0142: }
0143: }
0144:
0145: /**
0146: * Read property. Should return <code>null</code> if the key doesn't
0147: * map to an existing object.
0148: *
0149: * @param key key to use for mapping
0150: *
0151: * @return object associated with the given configuration key.
0152: */
0153: protected abstract Object getPropertyDirect(String key);
0154:
0155: /**
0156: * Adds a key/value pair to the Configuration. Override this method to
0157: * provide write acces to underlying Configuration store.
0158: *
0159: * @param key key to use for mapping
0160: * @param obj object to store
0161: */
0162: protected abstract void addPropertyDirect(String key, Object obj);
0163:
0164: /**
0165: * interpolate key names to handle ${key} stuff
0166: *
0167: * @param base string to interpolate
0168: *
0169: * @return returns the key name with the ${key} substituted
0170: */
0171: protected String interpolate(String base) {
0172: return (interpolateHelper(base, null));
0173: }
0174:
0175: /**
0176: * Recursive handler for multple levels of interpolation.
0177: *
0178: * When called the first time, priorVariables should be null.
0179: *
0180: * @param base string with the ${key} variables
0181: * @param priorVariables serves two purposes: to allow checking for
0182: * loops, and creating a meaningful exception message should a loop
0183: * occur. It's 0'th element will be set to the value of base from
0184: * the first call. All subsequent interpolated variables are added
0185: * afterward.
0186: *
0187: * @return the string with the interpolation taken care of
0188: */
0189: protected String interpolateHelper(String base, List priorVariables) {
0190: if (base == null) {
0191: return null;
0192: }
0193:
0194: // on the first call initialize priorVariables
0195: // and add base as the first element
0196: if (priorVariables == null) {
0197: priorVariables = new ArrayList();
0198: priorVariables.add(base);
0199: }
0200:
0201: int begin = -1;
0202: int end = -1;
0203: int prec = 0 - END_TOKEN.length();
0204: String variable = null;
0205: StringBuffer result = new StringBuffer();
0206:
0207: // FIXME: we should probably allow the escaping of the start token
0208: while (((begin = base.indexOf(START_TOKEN, prec
0209: + END_TOKEN.length())) > -1)
0210: && ((end = base.indexOf(END_TOKEN, begin)) > -1)) {
0211: result.append(base.substring(prec + END_TOKEN.length(),
0212: begin));
0213: variable = base
0214: .substring(begin + START_TOKEN.length(), end);
0215:
0216: // if we've got a loop, create a useful exception message and throw
0217: if (priorVariables.contains(variable)) {
0218: String initialBase = priorVariables.remove(0)
0219: .toString();
0220: priorVariables.add(variable);
0221: StringBuffer priorVariableSb = new StringBuffer();
0222:
0223: // create a nice trace of interpolated variables like so:
0224: // var1->var2->var3
0225: for (Iterator it = priorVariables.iterator(); it
0226: .hasNext();) {
0227: priorVariableSb.append(it.next());
0228: if (it.hasNext()) {
0229: priorVariableSb.append("->");
0230: }
0231: }
0232:
0233: throw new IllegalStateException(
0234: "infinite loop in property interpolation of "
0235: + initialBase + ": "
0236: + priorVariableSb.toString());
0237: }
0238: // otherwise, add this variable to the interpolation list.
0239: else {
0240: priorVariables.add(variable);
0241: }
0242:
0243: //QUESTION: getProperty or getPropertyDirect
0244: Object value = getProperty(variable);
0245: if (value != null) {
0246: result.append(interpolateHelper(value.toString(),
0247: priorVariables));
0248:
0249: // pop the interpolated variable off the stack
0250: // this maintains priorVariables correctness for
0251: // properties with multiple interpolations, e.g.
0252: // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
0253: priorVariables.remove(priorVariables.size() - 1);
0254: } else if (defaults != null
0255: && defaults.getString(variable, null) != null) {
0256: result.append(defaults.getString(variable));
0257: } else {
0258: //variable not defined - so put it back in the value
0259: result.append(START_TOKEN).append(variable).append(
0260: END_TOKEN);
0261: }
0262: prec = end;
0263: }
0264: result.append(base.substring(prec + END_TOKEN.length(), base
0265: .length()));
0266:
0267: return result.toString();
0268: }
0269:
0270: /**
0271: * Returns a Vector of Strings built from the supplied
0272: * String. Splits up CSV lists. If no commas are in the
0273: * String, simply returns a Vector with the String as its
0274: * first element
0275: *
0276: * @param token The String to tokenize
0277: *
0278: * @return A List of Strings
0279: */
0280: protected List processString(String token) {
0281: List retList = new ArrayList(INITIAL_LIST_SIZE);
0282:
0283: if (token.indexOf(PropertiesTokenizer.DELIMITER) > 0) {
0284: PropertiesTokenizer tokenizer = new PropertiesTokenizer(
0285: token);
0286:
0287: while (tokenizer.hasMoreTokens()) {
0288: String value = tokenizer.nextToken();
0289: retList.add(value);
0290: }
0291: } else {
0292: retList.add(token);
0293: }
0294:
0295: //
0296: // We keep the sequence of the keys here and
0297: // we also keep it in the Container. So the
0298: // Keys are added to the store in the sequence that
0299: // is given in the properties
0300: return retList;
0301: }
0302:
0303: /**
0304: * Test whether the string represent by value maps to a boolean
0305: * value or not. We will allow <code>true</code>, <code>on</code>,
0306: * and <code>yes</code> for a <code>true</code> boolean value, and
0307: * <code>false</code>, <code>off</code>, and <code>no</code> for
0308: * <code>false</code> boolean values. Case of value to test for
0309: * boolean status is ignored.
0310: *
0311: * @param value The value to test for boolean state.
0312: * @return <code>true</code> or <code>false</code> if the supplied
0313: * text maps to a boolean value, or <code>null</code> otherwise.
0314: */
0315: protected final Boolean testBoolean(String value) {
0316: String s = value.toLowerCase();
0317:
0318: if (s.equals("true") || s.equals("on") || s.equals("yes")) {
0319: return Boolean.TRUE;
0320: } else if (s.equals("false") || s.equals("off")
0321: || s.equals("no")) {
0322: return Boolean.FALSE;
0323: } else {
0324: return null;
0325: }
0326: }
0327:
0328: /**
0329: * Create an BaseConfiguration object that is a subset
0330: * of this one.
0331: *
0332: * @param prefix prefix string for keys
0333: *
0334: * @return subset of configuration if there is keys, that match
0335: * given prefix, or <code>null</code> if there is no such keys.
0336: */
0337: public Configuration subset(String prefix) {
0338: BaseConfiguration c = new BaseConfiguration();
0339: Iterator keys = this .getKeys();
0340: boolean validSubset = false;
0341:
0342: while (keys.hasNext()) {
0343: Object key = keys.next();
0344:
0345: if (key instanceof String
0346: && ((String) key).startsWith(prefix)) {
0347: if (!validSubset) {
0348: validSubset = true;
0349: }
0350:
0351: String newKey = null;
0352:
0353: /*
0354: * Check to make sure that c.subset(prefix) doesn't blow up when
0355: * there is only a single property with the key prefix. This is
0356: * not a useful subset but it is a valid subset.
0357: */
0358: if (((String) key).length() == prefix.length()) {
0359: newKey = prefix;
0360: } else {
0361: newKey = ((String) key)
0362: .substring(prefix.length() + 1);
0363: }
0364:
0365: /*
0366: * use addPropertyDirect() - this will plug the data as is into
0367: * the Map, but will also do the right thing re key accounting
0368: *
0369: * QUESTION: getProperty or getPropertyDirect
0370: */
0371: Object value = getProperty((String) key);
0372: if (value instanceof String) {
0373: c.addPropertyDirect(newKey,
0374: interpolate((String) value));
0375: } else {
0376: c.addProperty(newKey, value);
0377: }
0378: }
0379: }
0380:
0381: if (validSubset) {
0382: return c;
0383: } else {
0384: return null;
0385: }
0386: }
0387:
0388: /**
0389: * Check if the configuration is empty
0390: *
0391: * @return <code>true</code> if Configuration is empty,
0392: * <code>false</code> otherwise.
0393: */
0394: public abstract boolean isEmpty();
0395:
0396: /**
0397: * check if the configuration contains the key
0398: *
0399: * @param key the configuration key
0400: *
0401: * @return <code>true</code> if Configuration contain given key,
0402: * <code>false</code> otherwise.
0403: */
0404: public abstract boolean containsKey(String key);
0405:
0406: /**
0407: * Set a property, this will replace any previously
0408: * set values. Set values is implicitly a call
0409: * to clearProperty(key), addProperty(key,value).
0410: *
0411: * @param key the configuration key
0412: * @param value the property value
0413: */
0414: public void setProperty(String key, Object value) {
0415: clearProperty(key);
0416: addProperty(key, value); // QUESTION: or addPropertyDirect?
0417: }
0418:
0419: /**
0420: * Clear a property in the configuration.
0421: *
0422: * @param key the key to remove along with corresponding value.
0423: */
0424: public abstract void clearProperty(String key);
0425:
0426: /**
0427: * Get the list of the keys contained in the configuration
0428: * repository.
0429: *
0430: * @return An Iterator.
0431: */
0432: public abstract Iterator getKeys();
0433:
0434: /**
0435: * Get the list of the keys contained in the configuration
0436: * repository that match the specified prefix.
0437: *
0438: * @param prefix The prefix to test against.
0439: *
0440: * @return An Iterator of keys that match the prefix.
0441: */
0442: public Iterator getKeys(String prefix) {
0443: Iterator keys = getKeys();
0444: ArrayList matchingKeys = new ArrayList();
0445:
0446: while (keys.hasNext()) {
0447: Object key = keys.next();
0448:
0449: if (key instanceof String
0450: && ((String) key).startsWith(prefix)) {
0451: matchingKeys.add(key);
0452: }
0453: }
0454: return matchingKeys.iterator();
0455: }
0456:
0457: /**
0458: * Get a list of properties associated with the given
0459: * configuration key.
0460: *
0461: * @param key The configuration key.
0462: *
0463: * @return The associated properties if key is found.
0464: *
0465: * @throws ClassCastException is thrown if the key maps to an
0466: * object that is not a String/Vector.
0467: * @throws IllegalArgumentException if one of the tokens is
0468: * malformed (does not contain an equals sign).
0469: *
0470: * @see #getProperties(String, Properties)
0471: */
0472: public Properties getProperties(String key) {
0473: return getProperties(key, null);
0474: }
0475:
0476: /**
0477: * Get a list of properties associated with the given
0478: * configuration key.
0479: *
0480: * @param key The configuration key.
0481: * @param defaults Any default values for the returned
0482: * <code>Properties</code> object. Ignored if <code>null</code>.
0483: *
0484: * @return The associated properties if key is found.
0485: *
0486: * @throws ClassCastException is thrown if the key maps to an
0487: * object that is not a String/Vector of Strings.
0488: * @throws IllegalArgumentException if one of the tokens is
0489: * malformed (does not contain an equals sign).
0490: */
0491: public Properties getProperties(String key, Properties defaults) {
0492: /*
0493: * Grab an array of the tokens for this key.
0494: */
0495: String[] tokens = getStringArray(key);
0496:
0497: /*
0498: * Each token is of the form 'key=value'.
0499: */
0500: Properties props = (defaults == null ? new Properties()
0501: : new Properties(defaults));
0502: for (int i = 0; i < tokens.length; i++) {
0503: String token = tokens[i];
0504: int equalSign = token.indexOf('=');
0505: if (equalSign > 0) {
0506: String pkey = token.substring(0, equalSign).trim();
0507: String pvalue = token.substring(equalSign + 1).trim();
0508: props.put(pkey, pvalue);
0509: } else if (tokens.length == 1 && "".equals(token)) {
0510: // Semantically equivalent to an empty Properties
0511: // object.
0512: break;
0513: } else {
0514: throw new IllegalArgumentException('\'' + token
0515: + "' does not contain an equals sign");
0516: }
0517: }
0518: return props;
0519: }
0520:
0521: /**
0522: * Gets a property from the configuration.
0523: *
0524: * @param key property to retrieve
0525: * @return value as object. Will return user value if exists,
0526: * if not then default value if exists, otherwise null
0527: */
0528: public Object getProperty(String key) {
0529: // first, try to get from the 'user value' store
0530: Object o = getPropertyDirect(key);
0531:
0532: if (o == null) {
0533: // if there isn't a value there, get it from the defaults if we have
0534: // them
0535: if (defaults != null) {
0536: o = defaults.getProperty(key);
0537: }
0538: }
0539:
0540: //
0541: // We must never give a Container Object out. So if the
0542: // Return Value is a Container, we fix it up to be a
0543: // Vector
0544: //
0545: if (o instanceof Container) {
0546: o = ((Container) o).asVector();
0547: }
0548: return o;
0549: }
0550:
0551: /**
0552: * Get a boolean associated with the given configuration key.
0553: *
0554: * @param key The configuration key.
0555: *
0556: * @return The associated boolean.
0557: *
0558: * @throws NoSuchElementException is thrown if the key doesn't
0559: * map to an existing object.
0560: * @throws ClassCastException is thrown if the key maps to an
0561: * object that is not a Boolean.
0562: */
0563: public boolean getBoolean(String key) {
0564: Boolean b = getBoolean(key, (Boolean) null);
0565: if (b != null) {
0566: return b.booleanValue();
0567: } else {
0568: throw new NoSuchElementException('\'' + key
0569: + "' doesn't map to an existing object");
0570: }
0571: }
0572:
0573: /**
0574: * Get a boolean associated with the given configuration key.
0575: *
0576: * @param key The configuration key.
0577: * @param defaultValue The default value.
0578: *
0579: * @return The associated boolean.
0580: *
0581: * @throws ClassCastException is thrown if the key maps to an
0582: * object that is not a Boolean.
0583: */
0584: public boolean getBoolean(String key, boolean defaultValue) {
0585: return getBoolean(key, new Boolean(defaultValue))
0586: .booleanValue();
0587: }
0588:
0589: /**
0590: * Get a boolean associated with the given configuration key.
0591: *
0592: * @param key The configuration key.
0593: * @param defaultValue The default value.
0594: *
0595: * @return The associated boolean if key is found and has valid
0596: * format, default value otherwise.
0597: *
0598: * @throws ClassCastException is thrown if the key maps to an
0599: * object that is not a Boolean.
0600: */
0601: public Boolean getBoolean(String key, Boolean defaultValue) {
0602: Object value = resolveContainerStore(key);
0603:
0604: if (value instanceof Boolean) {
0605: return (Boolean) value;
0606: } else if (value instanceof String) {
0607: return testBoolean((String) value);
0608: } else if (value == null) {
0609: if (defaults != null) {
0610: return defaults.getBoolean(key, defaultValue);
0611: } else {
0612: log.warn("Use Boolean default value for key '" + key
0613: + "' (" + defaultValue + ")");
0614: return defaultValue;
0615: }
0616: } else {
0617: throw new ClassCastException('\'' + key
0618: + "' doesn't map to a Boolean object");
0619: }
0620: }
0621:
0622: /**
0623: * Get a byte associated with the given configuration key.
0624: *
0625: * @param key The configuration key.
0626: *
0627: * @return The associated byte.
0628: *
0629: * @throws NoSuchElementException is thrown if the key doesn't
0630: * map to an existing object.
0631: * @throws ClassCastException is thrown if the key maps to an
0632: * object that is not a Byte.
0633: * @throws NumberFormatException is thrown if the value mapped
0634: * by the key has not a valid number format.
0635: */
0636: public byte getByte(String key) {
0637: Byte b = getByte(key, null);
0638: if (b != null) {
0639: return b.byteValue();
0640: } else {
0641: throw new NoSuchElementException('\'' + key
0642: + " doesn't map to an existing object");
0643: }
0644: }
0645:
0646: /**
0647: * Get a byte associated with the given configuration key.
0648: *
0649: * @param key The configuration key.
0650: * @param defaultValue The default value.
0651: *
0652: * @return The associated byte.
0653: *
0654: * @throws ClassCastException is thrown if the key maps to an
0655: * object that is not a Byte.
0656: * @throws NumberFormatException is thrown if the value mapped
0657: * by the key has not a valid number format.
0658: */
0659: public byte getByte(String key, byte defaultValue) {
0660: return getByte(key, new Byte(defaultValue)).byteValue();
0661: }
0662:
0663: /**
0664: * Get a byte associated with the given configuration key.
0665: *
0666: * @param key The configuration key.
0667: * @param defaultValue The default value.
0668: *
0669: * @return The associated byte if key is found and has valid format, default
0670: * value otherwise.
0671: *
0672: * @throws ClassCastException is thrown if the key maps to an object that
0673: * is not a Byte.
0674: * @throws NumberFormatException is thrown if the value mapped by the key
0675: * has not a valid number format.
0676: */
0677: public Byte getByte(String key, Byte defaultValue) {
0678: Object value = resolveContainerStore(key);
0679:
0680: if (value instanceof Byte) {
0681: return (Byte) value;
0682: } else if (value instanceof String) {
0683: Byte b = new Byte((String) value);
0684: return b;
0685: } else if (value == null) {
0686: if (defaults != null) {
0687: return defaults.getByte(key, defaultValue);
0688: } else {
0689: log.warn("Use Byte default value for key '" + key
0690: + "' (" + defaultValue + ")");
0691: return defaultValue;
0692: }
0693: } else {
0694: throw new ClassCastException('\'' + key
0695: + "' doesn't map to a Byte object");
0696: }
0697: }
0698:
0699: /**
0700: * Get a double associated with the given configuration key.
0701: *
0702: * @param key The configuration key.
0703: *
0704: * @return The associated double.
0705: *
0706: * @throws NoSuchElementException is thrown if the key doesn't
0707: * map to an existing object.
0708: * @throws ClassCastException is thrown if the key maps to an
0709: * object that is not a Double.
0710: * @throws NumberFormatException is thrown if the value mapped
0711: * by the key has not a valid number format.
0712: */
0713: public double getDouble(String key) {
0714: Double d = getDouble(key, null);
0715: if (d != null) {
0716: return d.doubleValue();
0717: } else {
0718: throw new NoSuchElementException('\'' + key
0719: + "' doesn't map to an existing object");
0720: }
0721: }
0722:
0723: /**
0724: * Get a double associated with the given configuration key.
0725: *
0726: * @param key The configuration key.
0727: * @param defaultValue The default value.
0728: *
0729: * @return The associated double.
0730: *
0731: * @throws ClassCastException is thrown if the key maps to an
0732: * object that is not a Double.
0733: * @throws NumberFormatException is thrown if the value mapped
0734: * by the key has not a valid number format.
0735: */
0736: public double getDouble(String key, double defaultValue) {
0737: return getDouble(key, new Double(defaultValue)).doubleValue();
0738: }
0739:
0740: /**
0741: * Get a double associated with the given configuration key.
0742: *
0743: * @param key The configuration key.
0744: * @param defaultValue The default value.
0745: *
0746: * @return The associated double if key is found and has valid
0747: * format, default value otherwise.
0748: *
0749: * @throws ClassCastException is thrown if the key maps to an
0750: * object that is not a Double.
0751: * @throws NumberFormatException is thrown if the value mapped
0752: * by the key has not a valid number format.
0753: */
0754: public Double getDouble(String key, Double defaultValue) {
0755: Object value = resolveContainerStore(key);
0756:
0757: if (value instanceof Double) {
0758: return (Double) value;
0759: } else if (value instanceof String) {
0760: Double d = new Double((String) value);
0761: return d;
0762: } else if (value == null) {
0763: if (defaults != null) {
0764: return defaults.getDouble(key, defaultValue);
0765: } else {
0766: log.warn("Use Double default value for key '" + key
0767: + "' (" + defaultValue + ")");
0768: return defaultValue;
0769: }
0770: } else {
0771: throw new ClassCastException('\'' + key
0772: + "' doesn't map to a Double object");
0773: }
0774: }
0775:
0776: /**
0777: * Get a float associated with the given configuration key.
0778: *
0779: * @param key The configuration key.
0780: *
0781: * @return The associated float.
0782: *
0783: * @throws NoSuchElementException is thrown if the key doesn't
0784: * map to an existing object.
0785: * @throws ClassCastException is thrown if the key maps to an
0786: * object that is not a Float.
0787: * @throws NumberFormatException is thrown if the value mapped
0788: * by the key has not a valid number format.
0789: */
0790: public float getFloat(String key) {
0791: Float f = getFloat(key, null);
0792: if (f != null) {
0793: return f.floatValue();
0794: } else {
0795: throw new NoSuchElementException('\'' + key
0796: + "' doesn't map to an existing object");
0797: }
0798: }
0799:
0800: /**
0801: * Get a float associated with the given configuration key.
0802: *
0803: * @param key The configuration key.
0804: * @param defaultValue The default value.
0805: *
0806: * @return The associated float.
0807: *
0808: * @throws ClassCastException is thrown if the key maps to an
0809: * object that is not a Float.
0810: * @throws NumberFormatException is thrown if the value mapped
0811: * by the key has not a valid number format.
0812: */
0813: public float getFloat(String key, float defaultValue) {
0814: return getFloat(key, new Float(defaultValue)).floatValue();
0815: }
0816:
0817: /**
0818: * Get a float associated with the given configuration key.
0819: *
0820: * @param key The configuration key.
0821: * @param defaultValue The default value.
0822: *
0823: * @return The associated float if key is found and has valid
0824: * format, default value otherwise.
0825: *
0826: * @throws ClassCastException is thrown if the key maps to an
0827: * object that is not a Float.
0828: * @throws NumberFormatException is thrown if the value mapped
0829: * by the key has not a valid number format.
0830: */
0831: public Float getFloat(String key, Float defaultValue) {
0832: Object value = resolveContainerStore(key);
0833:
0834: if (value instanceof Float) {
0835: return (Float) value;
0836: } else if (value instanceof String) {
0837: Float f = new Float((String) value);
0838: return f;
0839: } else if (value == null) {
0840: if (defaults != null) {
0841: return defaults.getFloat(key, defaultValue);
0842: } else {
0843: log.warn("Use Float default value for key '" + key
0844: + "' (" + defaultValue + ")");
0845: return defaultValue;
0846: }
0847: } else {
0848: throw new ClassCastException('\'' + key
0849: + "' doesn't map to a Float object");
0850: }
0851: }
0852:
0853: /**
0854: * Get a int associated with the given configuration key.
0855: *
0856: * @param key The configuration key.
0857: *
0858: * @return The associated int.
0859: *
0860: * @throws NoSuchElementException is thrown if the key doesn't
0861: * map to an existing object.
0862: * @throws ClassCastException is thrown if the key maps to an
0863: * object that is not a Integer.
0864: * @throws NumberFormatException is thrown if the value mapped
0865: * by the key has not a valid number format.
0866: */
0867: public int getInt(String key) {
0868: Integer i = getInteger(key, null);
0869: if (i != null) {
0870: return i.intValue();
0871: } else {
0872: throw new NoSuchElementException('\'' + key
0873: + "' doesn't map to an existing object");
0874: }
0875: }
0876:
0877: /**
0878: * Get a int associated with the given configuration key.
0879: *
0880: * @param key The configuration key.
0881: * @param defaultValue The default value.
0882: *
0883: * @return The associated int.
0884: *
0885: * @throws ClassCastException is thrown if the key maps to an
0886: * object that is not a Integer.
0887: * @throws NumberFormatException is thrown if the value mapped
0888: * by the key has not a valid number format.
0889: */
0890: public int getInt(String key, int defaultValue) {
0891: Integer i = getInteger(key, null);
0892:
0893: if (i == null) {
0894: return defaultValue;
0895: }
0896:
0897: return i.intValue();
0898: }
0899:
0900: /**
0901: * Get a int associated with the given configuration key.
0902: *
0903: * @param key The configuration key.
0904: * @param defaultValue The default value.
0905: *
0906: * @return The associated int if key is found and has valid format, default
0907: * value otherwise.
0908: *
0909: * @throws ClassCastException is thrown if the key maps to an object that
0910: * is not a Integer.
0911: * @throws NumberFormatException is thrown if the value mapped by the key
0912: * has not a valid number format.
0913: */
0914: public Integer getInteger(String key, Integer defaultValue) {
0915: Object value = resolveContainerStore(key);
0916:
0917: if (value instanceof Integer) {
0918: return (Integer) value;
0919: } else if (value instanceof String) {
0920: Integer i = Integer.valueOf((String) value);
0921: return i;
0922: } else if (value == null) {
0923: if (defaults != null) {
0924: return defaults.getInteger(key, defaultValue);
0925: } else {
0926: log.warn("Use Integer default value for key '" + key
0927: + "' (" + defaultValue + ")");
0928: return defaultValue;
0929: }
0930: } else {
0931: throw new ClassCastException('\'' + key
0932: + "' doesn't map to a Integer object");
0933: }
0934: }
0935:
0936: /**
0937: * Get a long associated with the given configuration key.
0938: *
0939: * @param key The configuration key.
0940: *
0941: * @return The associated long.
0942: *
0943: * @throws NoSuchElementException is thrown if the key doesn't
0944: * map to an existing object.
0945: * @throws ClassCastException is thrown if the key maps to an
0946: * object that is not a Long.
0947: * @throws NumberFormatException is thrown if the value mapped
0948: * by the key has not a valid number format.
0949: */
0950: public long getLong(String key) {
0951: Long l = getLong(key, null);
0952: if (l != null) {
0953: return l.longValue();
0954: } else {
0955: throw new NoSuchElementException('\'' + key
0956: + "' doesn't map to an existing object");
0957: }
0958: }
0959:
0960: /**
0961: * Get a long associated with the given configuration key.
0962: *
0963: * @param key The configuration key.
0964: * @param defaultValue The default value.
0965: *
0966: * @return The associated long.
0967: *
0968: * @throws ClassCastException is thrown if the key maps to an
0969: * object that is not a Long.
0970: * @throws NumberFormatException is thrown if the value mapped
0971: * by the key has not a valid number format.
0972: */
0973: public long getLong(String key, long defaultValue) {
0974: return getLong(key, new Long(defaultValue)).longValue();
0975: }
0976:
0977: /**
0978: * Get a long associated with the given configuration key.
0979: *
0980: * @param key The configuration key.
0981: * @param defaultValue The default value.
0982: *
0983: * @return The associated long if key is found and has valid
0984: * format, default value otherwise.
0985: *
0986: * @throws ClassCastException is thrown if the key maps to an
0987: * object that is not a Long.
0988: * @throws NumberFormatException is thrown if the value mapped
0989: * by the key has not a valid number format.
0990: */
0991: public Long getLong(String key, Long defaultValue) {
0992: Object value = resolveContainerStore(key);
0993:
0994: if (value instanceof Long) {
0995: return (Long) value;
0996: } else if (value instanceof String) {
0997: Long l = new Long((String) value);
0998: return l;
0999: } else if (value == null) {
1000: if (defaults != null) {
1001: return defaults.getLong(key, defaultValue);
1002: } else {
1003: log.warn("Use Long default value for key '" + key
1004: + "' (" + defaultValue + ")");
1005: return defaultValue;
1006: }
1007: } else {
1008: throw new ClassCastException('\'' + key
1009: + "' doesn't map to a Long object");
1010: }
1011: }
1012:
1013: /**
1014: * Get a short associated with the given configuration key.
1015: *
1016: * @param key The configuration key.
1017: *
1018: * @return The associated short.
1019: *
1020: * @throws NoSuchElementException is thrown if the key doesn't
1021: * map to an existing object.
1022: * @throws ClassCastException is thrown if the key maps to an
1023: * object that is not a Short.
1024: * @throws NumberFormatException is thrown if the value mapped
1025: * by the key has not a valid number format.
1026: */
1027: public short getShort(String key) {
1028: Short s = getShort(key, null);
1029: if (s != null) {
1030: return s.shortValue();
1031: } else {
1032: throw new NoSuchElementException('\'' + key
1033: + "' doesn't map to an existing object");
1034: }
1035: }
1036:
1037: /**
1038: * Get a short associated with the given configuration key.
1039: *
1040: * @param key The configuration key.
1041: * @param defaultValue The default value.
1042: *
1043: * @return The associated short.
1044: *
1045: * @throws ClassCastException is thrown if the key maps to an
1046: * object that is not a Short.
1047: * @throws NumberFormatException is thrown if the value mapped
1048: * by the key has not a valid number format.
1049: */
1050: public short getShort(String key, short defaultValue) {
1051: return getShort(key, new Short(defaultValue)).shortValue();
1052: }
1053:
1054: /**
1055: * Get a short associated with the given configuration key.
1056: *
1057: * @param key The configuration key.
1058: * @param defaultValue The default value.
1059: *
1060: * @return The associated short if key is found and has valid
1061: * format, default value otherwise.
1062: *
1063: * @throws ClassCastException is thrown if the key maps to an
1064: * object that is not a Short.
1065: * @throws NumberFormatException is thrown if the value mapped
1066: * by the key has not a valid number format.
1067: */
1068: public Short getShort(String key, Short defaultValue) {
1069: Object value = resolveContainerStore(key);
1070:
1071: if (value instanceof Short) {
1072: return (Short) value;
1073: } else if (value instanceof String) {
1074: Short s = new Short((String) value);
1075: return s;
1076: } else if (value == null) {
1077: if (defaults != null) {
1078: return defaults.getShort(key, defaultValue);
1079: } else {
1080: log.warn("Use Short default value for key '" + key
1081: + "' (" + defaultValue + ")");
1082: return defaultValue;
1083: }
1084: } else {
1085: throw new ClassCastException('\'' + key
1086: + "' doesn't map to a Short object");
1087: }
1088: }
1089:
1090: /**
1091: * Get a string associated with the given configuration key.
1092: *
1093: * @param key The configuration key.
1094: *
1095: * @return The associated string.
1096: *
1097: * @throws ClassCastException is thrown if the key maps to an object that
1098: * is not a String.
1099: * @throws NoSuchElementException is thrown if the key doesn't
1100: * map to an existing object.
1101: */
1102: public String getString(String key) {
1103: String s = getString(key, null);
1104: if (s != null) {
1105: return s;
1106: } else {
1107: throw new NoSuchElementException('\'' + key
1108: + "' doesn't map to an existing object");
1109: }
1110: }
1111:
1112: /**
1113: * Get a string associated with the given configuration key.
1114: *
1115: * @param key The configuration key.
1116: * @param defaultValue The default value.
1117: *
1118: * @return The associated string if key is found, default value otherwise.
1119: *
1120: * @throws ClassCastException is thrown if the key maps to an object that
1121: * is not a String.
1122: */
1123: public String getString(String key, String defaultValue) {
1124: Object value = resolveContainerStore(key);
1125:
1126: if (value instanceof String) {
1127: return interpolate((String) value);
1128: } else if (value == null) {
1129: if (defaults != null) {
1130: return interpolate(defaults
1131: .getString(key, defaultValue));
1132: } else {
1133: log.warn("Use String default value for key '" + key
1134: + "' (" + defaultValue + ")");
1135: return interpolate(defaultValue);
1136: }
1137: } else {
1138: throw new ClassCastException('\'' + key
1139: + "' doesn't map to a String object");
1140: }
1141: }
1142:
1143: /**
1144: * Get an array of strings associated with the given configuration
1145: * key.
1146: *
1147: * @param key The configuration key.
1148: *
1149: * @return The associated string array if key is found.
1150: *
1151: * @throws ClassCastException is thrown if the key maps to an
1152: * object that is not a String/Vector of Strings.
1153: */
1154: public String[] getStringArray(String key) {
1155: Object value = getPropertyDirect(key);
1156:
1157: String[] tokens;
1158:
1159: if (value instanceof String) {
1160: tokens = new String[1];
1161:
1162: tokens[0] = interpolate((String) value);
1163: } else if (value instanceof Container) {
1164: tokens = new String[((Container) value).size()];
1165:
1166: for (int i = 0; i < tokens.length; i++) {
1167: tokens[i] = interpolate((String) ((Container) value)
1168: .get(i));
1169: }
1170: } else if (value == null) {
1171: if (defaults != null) {
1172: tokens = defaults.getStringArray(key);
1173: } else {
1174: tokens = new String[0];
1175: }
1176: } else {
1177: throw new ClassCastException('\'' + key
1178: + "' doesn't map to a String/Vector object");
1179: }
1180: return tokens;
1181: }
1182:
1183: /**
1184: * Get a Vector of strings associated with the given configuration key.
1185: *
1186: * @param key The configuration key.
1187: *
1188: * @return The associated Vector.
1189: *
1190: * @throws ClassCastException is thrown if the key maps to an
1191: * object that is not a Vector.
1192: * @throws NoSuchElementException is thrown if the key doesn't
1193: * map to an existing object.
1194: */
1195: public Vector getVector(String key) {
1196: Vector v = getVector(key, null);
1197: if (v != null) {
1198: return v;
1199: } else {
1200: throw new NoSuchElementException('\'' + key
1201: + "' doesn't map to an existing object");
1202: }
1203: }
1204:
1205: /**
1206: * Get a Vector of strings associated with the given configuration key.
1207: *
1208: * @param key The configuration key.
1209: * @param defaultValue The default value.
1210: *
1211: * @return The associated Vector.
1212: *
1213: * @throws ClassCastException is thrown if the key maps to an
1214: * object that is not a Vector.
1215: */
1216: public Vector getVector(String key, Vector defaultValue) {
1217: Object value = getPropertyDirect(key);
1218: Vector v = null;
1219:
1220: if (value instanceof String) {
1221: v = new Vector(1);
1222: v.addElement(value);
1223: } else if (value instanceof Container) {
1224: v = ((Container) value).asVector();
1225: } else if (value == null) {
1226: if (defaults != null) {
1227: v = defaults.getVector(key, defaultValue);
1228: } else {
1229: v = ((defaultValue == null) ? new Vector()
1230: : defaultValue);
1231: }
1232: } else {
1233: throw new ClassCastException('\'' + key
1234: + "' doesn't map to a Vector object: " + value
1235: + ", a " + value.getClass().getName());
1236: }
1237: return v;
1238: }
1239:
1240: /**
1241: * Returns an object from the store described by the key.
1242: * If the value is a Container object, replace it with the
1243: * first object in the container
1244: *
1245: * @param key The property key.
1246: *
1247: * @return value Value, transparently resolving a possible
1248: * Container dependency.
1249: */
1250: private Object resolveContainerStore(String key) {
1251: Object value = getPropertyDirect(key);
1252: if (value != null && value instanceof Container) {
1253: value = ((Container) value).get(0);
1254: }
1255: return value;
1256: }
1257:
1258: /**
1259: * This class divides into tokens a property value. Token
1260: * separator is "," but commas into the property value are escaped
1261: * using the backslash in front.
1262: */
1263: class PropertiesTokenizer extends StringTokenizer {
1264: /** The property delimiter used while parsing (a comma). */
1265: static final String DELIMITER = ",";
1266:
1267: /**
1268: * Constructor.
1269: *
1270: * @param string A String.
1271: */
1272: public PropertiesTokenizer(String string) {
1273: super (string, DELIMITER);
1274: }
1275:
1276: /**
1277: * Check whether the object has more tokens.
1278: *
1279: * @return True if the object has more tokens.
1280: */
1281: public boolean hasMoreTokens() {
1282: return super .hasMoreTokens();
1283: }
1284:
1285: /**
1286: * Get next token.
1287: *
1288: * @return A String.
1289: */
1290: public String nextToken() {
1291: StringBuffer buffer = new StringBuffer();
1292:
1293: while (hasMoreTokens()) {
1294: String token = super .nextToken();
1295: if (token.endsWith("\\")) {
1296: buffer.append(token
1297: .substring(0, token.length() - 1));
1298: buffer.append(DELIMITER);
1299: } else {
1300: buffer.append(token);
1301: break;
1302: }
1303: }
1304: return buffer.toString().trim();
1305: }
1306: } // class PropertiesTokenizer
1307:
1308: /**
1309: * Private Wrapper class for Vector, so we can distinguish between
1310: * Vector objects and our container
1311: */
1312: static class Container {
1313: /** We're wrapping a List object (A vector) */
1314: private List l = null;
1315:
1316: /**
1317: * C'tor
1318: */
1319: public Container() {
1320: l = new Vector(INITIAL_LIST_SIZE);
1321: }
1322:
1323: /**
1324: * Add an Object to the Container
1325: *
1326: * @param o The Object
1327: */
1328: public void add(Object o) {
1329: l.add(o);
1330: }
1331:
1332: /**
1333: * Returns the current size of the Container
1334: *
1335: * @return The Number of elements in the container
1336: */
1337: public int size() {
1338: return l.size();
1339: }
1340:
1341: /**
1342: * Returns the Element at an index
1343: *
1344: * @param index The Index
1345: * @return The element at that index
1346: */
1347: public Object get(int index) {
1348: return l.get(index);
1349: }
1350:
1351: /**
1352: * Returns an Iterator over the container objects
1353: *
1354: * @return An Iterator
1355: */
1356: public Iterator iterator() {
1357: return l.iterator();
1358: }
1359:
1360: /**
1361: * Returns the Elements of the Container as
1362: * a Vector. This is not the internal vector
1363: * element but a shallow copy of the internal
1364: * list. You may modify the returned list without
1365: * modifying the container.
1366: *
1367: * @return A Vector containing the elements of the Container.
1368: */
1369: public Vector asVector() {
1370: Vector v = new Vector(l.size());
1371:
1372: for (Iterator it = l.iterator(); it.hasNext();) {
1373: v.add(it.next());
1374: }
1375: return v;
1376: }
1377: }
1378: }
|