/* * Copyright 2001-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.collections; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; /** * This class extends normal Java properties by adding the possibility * to use the same key many times concatenating the value strings * instead of overwriting them. * <p> * <b>Please consider using the <code>PropertiesConfiguration</code> class in * Commons-Configuration as soon as it is released.</b> * <p> * The Extended Properties syntax is explained here: * * <ul> * <li> * Each property has the syntax <code>key = value</code> * </li> * <li> * The <i>key</i> may use any character but the equal sign '='. * </li> * <li> * <i>value</i> may be separated on different lines if a backslash * is placed at the end of the line that continues below. * </li> * <li> * If <i>value</i> is a list of strings, each token is separated * by a comma ','. * </li> * <li> * Commas in each token are escaped placing a backslash right before * the comma. * </li> * <li> * Backslashes are escaped by using two consecutive backslashes i.e. \\ * </li> * <li> * If a <i>key</i> is used more than once, the values are appended * as if they were on the same line separated with commas. * </li> * <li> * Blank lines and lines starting with character '#' are skipped. * </li> * <li> * If a property is named "include" (or whatever is defined by * setInclude() and getInclude() and the value of that property is * the full path to a file on disk, that file will be included into * the ConfigurationsRepository. You can also pull in files relative * to the parent configuration file. So if you have something * like the following: * * include = additional.properties * * Then "additional.properties" is expected to be in the same * directory as the parent configuration file. * * Duplicate name values will be replaced, so be careful. * * </li> * </ul> * * <p>Here is an example of a valid extended properties file: * * <p><pre> * # lines starting with # are comments * * # This is the simplest property * key = value * * # A long property may be separated on multiple lines * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * * # This is a property with many tokens * tokens_on_a_line = first token, second token * * # This sequence generates exactly the same result * tokens_on_multiple_lines = first token * tokens_on_multiple_lines = second token * * # commas may be escaped in tokens * commas.escaped = Hi\, what'up? * </pre> * * <p><b>NOTE</b>: this class has <b>not</b> been written for * performance nor low memory usage. In fact, it's way slower than it * could be and generates too much memory garbage. But since * performance is not an issue during intialization (and there is not * much time to improve it), I wrote it this way. If you don't like * it, go ahead and tune it up! * * @since Commons Collections 1.0 * @version $Revision: 405927 $ $Date: 2006-05-12 23:57:03 +0100 (Fri, 12 May 2006) $ * * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a> * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a> * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a> * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a> * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a> * @author Janek Bogucki * @author Mohan Kishore * @author Stephen Colebourne * @author Shinobu Kawai * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> */ public class ExtendedProperties extends Hashtable { /** * Default configurations repository. */ private ExtendedProperties defaults; /** * The file connected to this repository (holding comments and * such). * * @serial */ protected String file; /** * Base path of the configuration file used to create * this ExtendedProperties object. */ protected String basePath; /** * File separator. */ protected String fileSeparator = System.getProperty("file.separator"); /** * Has this configuration been intialized. */ protected boolean isInitialized = false; /** * This is the name of the property that can point to other * properties file for including other properties files. */ protected static String include = "include"; /** * These are the keys in the order they listed * in the configuration file. This is useful when * you wish to perform operations with configuration * information in a particular order. */ protected ArrayList keysAsListed = new ArrayList(); protected final static String START_TOKEN="${"; protected final static String END_TOKEN="}"; /** * Interpolate key names to handle ${key} stuff * * @param base string to interpolate * @return returns the key name with the ${key} substituted */ protected String interpolate(String base) { // COPIED from [configuration] 2003-12-29 return (interpolateHelper(base, null)); } /** * Recursive handler for multiple levels of interpolation. * * When called the first time, priorVariables should be null. * * @param base string with the ${key} variables * @param priorVariables serves two purposes: to allow checking for * loops, and creating a meaningful exception message should a loop * occur. It's 0'th element will be set to the value of base from * the first call. All subsequent interpolated variables are added * afterward. * * @return the string with the interpolation taken care of */ protected String interpolateHelper(String base, List priorVariables) { // COPIED from [configuration] 2003-12-29 if (base == null) { return null; } // on the first call initialize priorVariables // and add base as the first element if (priorVariables == null) { priorVariables = new ArrayList(); priorVariables.add(base); } int begin = -1; int end = -1; int prec = 0 - END_TOKEN.length(); String variable = null; StringBuffer result = new StringBuffer(); // FIXME: we should probably allow the escaping of the start token while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1) && ((end = base.indexOf(END_TOKEN, begin)) > -1)) { result.append(base.substring(prec + END_TOKEN.length(), begin)); variable = base.substring(begin + START_TOKEN.length(), end); // if we've got a loop, create a useful exception message and throw if (priorVariables.contains(variable)) { String initialBase = priorVariables.remove(0).toString(); priorVariables.add(variable); StringBuffer priorVariableSb = new StringBuffer(); // create a nice trace of interpolated variables like so: // var1->var2->var3 for (Iterator it = priorVariables.iterator(); it.hasNext();) { priorVariableSb.append(it.next()); if (it.hasNext()) { priorVariableSb.append("->"); } } throw new IllegalStateException( "infinite loop in property interpolation of " + initialBase + ": " + priorVariableSb.toString()); } // otherwise, add this variable to the interpolation list. else { priorVariables.add(variable); } //QUESTION: getProperty or getPropertyDirect Object value = getProperty(variable); if (value != null) { result.append(interpolateHelper(value.toString(), priorVariables)); // pop the interpolated variable off the stack // this maintains priorVariables correctness for // properties with multiple interpolations, e.g. // prop.name=${some.other.prop1}/blahblah/${some.other.prop2} priorVariables.remove(priorVariables.size() - 1); } else if (defaults != null && defaults.getString(variable, null) != null) { result.append(defaults.getString(variable)); } else { //variable not defined - so put it back in the value result.append(START_TOKEN).append(variable).append(END_TOKEN); } prec = end; } result.append(base.substring(prec + END_TOKEN.length(), base.length())); return result.toString(); } /** * Inserts a backslash before every comma and backslash. */ private static String escape(String s) { StringBuffer buf = new StringBuffer(s); for (int i = 0; i < buf.length(); i++) { char c = buf.charAt(i); if (c == ',' || c == '\\') { buf.insert(i, '\\'); i++; } } return buf.toString(); } /** * Removes a backslash from every pair of backslashes. */ private static String unescape(String s) { StringBuffer buf = new StringBuffer(s); for (int i = 0; i < buf.length() - 1; i++) { char c1 = buf.charAt(i); char c2 = buf.charAt(i + 1); if (c1 == '\\' && c2 == '\\') { buf.deleteCharAt(i); } } return buf.toString(); } /** * Counts the number of successive times 'ch' appears in the * 'line' before the position indicated by the 'index'. */ private static int countPreceding(String line, int index, char ch) { int i; for (i = index - 1; i >= 0; i--) { if (line.charAt(i) != ch) { break; } } return index - 1 - i; } /** * Checks if the line ends with odd number of backslashes */ private static boolean endsWithSlash(String line) { if (!line.endsWith("\\")) { return false; } return (countPreceding(line, line.length() - 1, '\\') % 2 == 0); } /** * This class is used to read properties lines. These lines do * not terminate with new-line chars but rather when there is no * backslash sign a the end of the line. This is used to * concatenate multiple lines for readability. */ static class PropertiesReader extends LineNumberReader { /** * Constructor. * * @param reader A Reader. */ public PropertiesReader(Reader reader) { super(reader); } /** * Read a property. * * @return a String property * @throws IOException if there is difficulty reading the source. */ public String readProperty() throws IOException { StringBuffer buffer = new StringBuffer(); String line = readLine(); while (line != null) { line = line.trim(); if ((line.length() != 0) && (line.charAt(0) != '#')) { if (endsWithSlash(line)) { line = line.substring(0, line.length() - 1); buffer.append(line); } else { buffer.append(line); return buffer.toString(); // normal method end } } line = readLine(); } return null; // EOF reached } } /** * This class divides into tokens a property value. Token * separator is "," but commas into the property value are escaped * using the backslash in front. */ static class PropertiesTokenizer extends StringTokenizer { /** * The property delimiter used while parsing (a comma). */ static final String DELIMITER = ","; /** * Constructor. * * @param string A String. */ public PropertiesTokenizer(String string) { super(string, DELIMITER); } /** * Check whether the object has more tokens. * * @return True if the object has more tokens. */ public boolean hasMoreTokens() { return super.hasMoreTokens(); } /** * Get next token. * * @return A String. */ public String nextToken() { StringBuffer buffer = new StringBuffer(); while (hasMoreTokens()) { String token = super.nextToken(); if (endsWithSlash(token)) { buffer.append(token.substring(0, token.length() - 1)); buffer.append(DELIMITER); } else { buffer.append(token); break; } } return buffer.toString().trim(); } } /** * Creates an empty extended properties object. */ public ExtendedProperties() { super(); } /** * Creates and loads the extended properties from the specified file. * * @param file the filename to load * @throws IOException if a file error occurs */ public ExtendedProperties(String file) throws IOException { this(file, null); } /** * Creates and loads the extended properties from the specified file. * * @param file the filename to load * @param defaultFile a second filename to load default values from * @throws IOException if a file error occurs */ public ExtendedProperties(String file, String defaultFile) throws IOException { this.file = file; basePath = new File(file).getAbsolutePath(); basePath = basePath.substring(0, basePath.lastIndexOf(fileSeparator) + 1); FileInputStream in = null; try { in = new FileInputStream(file); this.load(in); } finally { try { if (in != null) { in.close(); } } catch (IOException ex) {} } if (defaultFile != null) { defaults = new ExtendedProperties(defaultFile); } } /** * Indicate to client code whether property * resources have been initialized or not. */ public boolean isInitialized() { return isInitialized; } /** * Gets the property value for including other properties files. * By default it is "include". * * @return A String. */ public String getInclude() { return include; } /** * Sets the property value for including other properties files. * By default it is "include". * * @param inc A String. */ public void setInclude(String inc) { include = inc; } /** * Load the properties from the given input stream. * * @param input the InputStream to load from * @throws IOException if an IO error occurs */ public void load(InputStream input) throws IOException { load(input, null); } /** * Load the properties from the given input stream * and using the specified encoding. * * @param input the InputStream to load from * @param enc the encoding to use * @throws IOException if an IO error occurs */ public synchronized void load(InputStream input, String enc) throws IOException { PropertiesReader reader = null; if (enc != null) { try { reader = new PropertiesReader(new InputStreamReader(input, enc)); } catch (UnsupportedEncodingException ex) { // Another try coming up.... } } if (reader == null) { try { reader = new PropertiesReader(new InputStreamReader(input, "8859_1")); } catch (UnsupportedEncodingException ex) { // ISO8859-1 support is required on java platforms but.... // If it's not supported, use the system default encoding reader = new PropertiesReader(new InputStreamReader(input)); } } try { while (true) { String line = reader.readProperty(); if (line == null) { return; // EOF } int equalSign = line.indexOf('='); if (equalSign > 0) { String key = line.substring(0, equalSign).trim(); String value = line.substring(equalSign + 1).trim(); // Configure produces lines like this ... just ignore them if ("".equals(value)) { continue; } if (getInclude() != null && key.equalsIgnoreCase(getInclude())) { // Recursively load properties files. File file = null; if (value.startsWith(fileSeparator)) { // We have an absolute path so we'll use this file = new File(value); } else { // We have a relative path, and we have two // possible forms here. If we have the "./" form // then just strip that off first before continuing. if (value.startsWith("." + fileSeparator)) { value = value.substring(2); } file = new File(basePath + value); } if (file != null && file.exists() && file.canRead()) { load(new FileInputStream(file)); } } else { addProperty(key, value); } } } } finally { // Loading is initializing isInitialized = true; } } /** * Gets a property from the configuration. * * @param key property to retrieve * @return value as object. Will return user value if exists, * if not then default value if exists, otherwise null */ public Object getProperty(String key) { // first, try to get from the 'user value' store Object obj = this.get(key); if (obj == null) { // if there isn't a value there, get it from the // defaults if we have them if (defaults != null) { obj = defaults.get(key); } } return obj; } /** * Add a property to the configuration. If it already * exists then the value stated here will be added * to the configuration entry. For example, if * * <code>resource.loader = file</code> * * is already present in the configuration and you * * <code>addProperty("resource.loader", "classpath")</code> * * Then you will end up with a Vector like the * following: * * <code>["file", "classpath"]</code> * * @param key the key to add * @param value the value to add */ public void addProperty(String key, Object value) { if (value instanceof String) { String str = (String) value; if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0) { // token contains commas, so must be split apart then added PropertiesTokenizer tokenizer = new PropertiesTokenizer(str); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); addPropertyInternal(key, unescape(token)); } } else { // token contains no commas, so can be simply added addPropertyInternal(key, unescape(str)); } } else { addPropertyInternal(key, value); } // Adding a property connotes initialization isInitialized = true; } /** * Adds a key/value pair to the map. This routine does * no magic morphing. It ensures the keylist is maintained * * @param key the key to store at * @param value the decoded object to store */ private void addPropertyDirect(String key, Object value) { // safety check if (!containsKey(key)) { keysAsListed.add(key); } put(key, value); } /** * Adds a decoded property to the map w/o checking for commas - used * internally when a property has been broken up into * strings that could contain escaped commas to prevent * the inadvertent vectorization. * <p> * Thanks to Leon Messerschmidt for this one. * * @param key the key to store at * @param value the decoded object to store */ private void addPropertyInternal(String key, Object value) { Object current = this.get(key); if (current instanceof String) { // one object already in map - convert it to a vector List values = new Vector(2); values.add(current); values.add(value); put(key, values); } else if (current instanceof List) { // already a list - just add the new token ((List) current).add(value); } else { // brand new key - store in keysAsListed to retain order if (!containsKey(key)) { keysAsListed.add(key); } put(key, value); } } /** * Set a property, this will replace any previously * set values. Set values is implicitly a call * to clearProperty(key), addProperty(key,value). * * @param key the key to set * @param value the value to set */ public void setProperty(String key, Object value) { clearProperty(key); addProperty(key, value); } /** * Save the properties to the given output stream. * <p> * The stream is not closed, but it is flushed. * * @param output an OutputStream, may be null * @param header a textual comment to act as a file header * @throws IOException if an IO error occurs */ public synchronized void save(OutputStream output, String header) throws IOException { if (output == null) { return; } PrintWriter theWrtr = new PrintWriter(output); if (header != null) { theWrtr.println(header); } Enumeration theKeys = keys(); while (theKeys.hasMoreElements()) { String key = (String) theKeys.nextElement(); Object value = get(key); if (value != null) { if (value instanceof String) { StringBuffer currentOutput = new StringBuffer(); currentOutput.append(key); currentOutput.append("="); currentOutput.append(escape((String) value)); theWrtr.println(currentOutput.toString()); } else if (value instanceof List) { List values = (List) value; for (Iterator it = values.iterator(); it.hasNext(); ) { String currentElement = (String) it.next(); StringBuffer currentOutput = new StringBuffer(); currentOutput.append(key); currentOutput.append("="); currentOutput.append(escape(currentElement)); theWrtr.println(currentOutput.toString()); } } } theWrtr.println(); theWrtr.flush(); } } /** * Combines an existing Hashtable with this Hashtable. * <p> * Warning: It will overwrite previous entries without warning. * * @param props the properties to combine */ public void combine(ExtendedProperties props) { for (Iterator it = props.getKeys(); it.hasNext();) { String key = (String) it.next(); setProperty(key, props.get(key)); } } /** * Clear a property in the configuration. * * @param key the property key to remove along with corresponding value */ public void clearProperty(String key) { if (containsKey(key)) { // we also need to rebuild the keysAsListed or else // things get *very* confusing for (int i = 0; i < keysAsListed.size(); i++) { if (( keysAsListed.get(i)).equals(key)) { keysAsListed.remove(i); break; } } remove(key); } } /** * Get the list of the keys contained in the configuration * repository. * * @return an Iterator over the keys */ public Iterator getKeys() { return keysAsListed.iterator(); } /** * Get the list of the keys contained in the configuration * repository that match the specified prefix. * * @param prefix the prefix to match * @return an Iterator of keys that match the prefix */ public Iterator getKeys(String prefix) { Iterator keys = getKeys(); ArrayList matchingKeys = new ArrayList(); while (keys.hasNext()) { Object key = keys.next(); if (key instanceof String && ((String) key).startsWith(prefix)) { matchingKeys.add(key); } } return matchingKeys.iterator(); } /** * Create an ExtendedProperties object that is a subset * of this one. Take into account duplicate keys * by using the setProperty() in ExtendedProperties. * * @param prefix the prefix to get a subset for * @return a new independent ExtendedProperties */ public ExtendedProperties subset(String prefix) { ExtendedProperties c = new ExtendedProperties(); Iterator keys = getKeys(); boolean validSubset = false; while (keys.hasNext()) { Object key = keys.next(); if (key instanceof String && ((String) key).startsWith(prefix)) { if (!validSubset) { validSubset = true; } /* * Check to make sure that c.subset(prefix) doesn't * blow up when there is only a single property * with the key prefix. This is not a useful * subset but it is a valid subset. */ String newKey = null; if (((String) key).length() == prefix.length()) { newKey = prefix; } else { newKey = ((String) key).substring(prefix.length() + 1); } /* * use addPropertyDirect() - this will plug the data as * is into the Map, but will also do the right thing * re key accounting */ c.addPropertyDirect(newKey, get(key)); } } if (validSubset) { return c; } else { return null; } } /** * Display the configuration for debugging purposes to System.out. */ public void display() { Iterator i = getKeys(); while (i.hasNext()) { String key = (String) i.next(); Object value = get(key); System.out.println(key + " => " + value); } } /** * Get a string associated with the given configuration key. * * @param key The configuration key. * @return The associated string. * @throws ClassCastException is thrown if the key maps to an * object that is not a String. */ public String getString(String key) { return getString(key, null); } /** * Get a string associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated string if key is found, * default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a String. */ public String getString(String key, String defaultValue) { Object value = get(key); if (value instanceof String) { return interpolate((String) value); } else if (value == null) { if (defaults != null) { return interpolate(defaults.getString(key, defaultValue)); } else { return interpolate(defaultValue); } } else if (value instanceof List) { return interpolate((String) ((List) value).get(0)); } else { throw new ClassCastException('\'' + key + "' doesn't map to a String object"); } } /** * Get a list of properties associated with the given * configuration key. * * @param key The configuration key. * @return The associated properties if key is found. * @throws ClassCastException is thrown if the key maps to an * object that is not a String/List. * @throws IllegalArgumentException if one of the tokens is * malformed (does not contain an equals sign). */ public Properties getProperties(String key) { return getProperties(key, new Properties()); } /** * Get a list of properties associated with the given * configuration key. * * @param key The configuration key. * @return The associated properties if key is found. * @throws ClassCastException is thrown if the key maps to an * object that is not a String/List. * @throws IllegalArgumentException if one of the tokens is * malformed (does not contain an equals sign). */ public Properties getProperties(String key, Properties defaults) { /* * Grab an array of the tokens for this key. */ String[] tokens = getStringArray(key); // Each token is of the form 'key=value'. Properties props = new Properties(defaults); for (int i = 0; i < tokens.length; i++) { String token = tokens[i]; int equalSign = token.indexOf('='); if (equalSign > 0) { String pkey = token.substring(0, equalSign).trim(); String pvalue = token.substring(equalSign + 1).trim(); props.put(pkey, pvalue); } else { throw new IllegalArgumentException('\'' + token + "' does not contain " + "an equals sign"); } } return props; } /** * Get an array of strings associated with the given configuration * key. * * @param key The configuration key. * @return The associated string array if key is found. * @throws ClassCastException is thrown if the key maps to an * object that is not a String/List. */ public String[] getStringArray(String key) { Object value = get(key); List values; if (value instanceof String) { values = new Vector(1); values.add(value); } else if (value instanceof List) { values = (List) value; } else if (value == null) { if (defaults != null) { return defaults.getStringArray(key); } else { return new String[0]; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a String/List object"); } String[] tokens = new String[values.size()]; for (int i = 0; i < tokens.length; i++) { tokens[i] = (String) values.get(i); } return tokens; } /** * Get a Vector of strings associated with the given configuration * key. * * @param key The configuration key. * @return The associated Vector. * @throws ClassCastException is thrown if the key maps to an * object that is not a Vector. */ public Vector getVector(String key) { return getVector(key, null); } /** * Get a Vector of strings associated with the given configuration key. * <p> * The list is a copy of the internal data of this object, and as * such you may alter it freely. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated Vector. * @throws ClassCastException is thrown if the key maps to an * object that is not a Vector. */ public Vector getVector(String key, Vector defaultValue) { Object value = get(key); if (value instanceof List) { return new Vector((List) value); } else if (value instanceof String) { Vector values = new Vector(1); values.add(value); put(key, values); return values; } else if (value == null) { if (defaults != null) { return defaults.getVector(key, defaultValue); } else { return ((defaultValue == null) ? new Vector() : defaultValue); } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Vector object"); } } /** * Get a List of strings associated with the given configuration key. * <p> * The list is a copy of the internal data of this object, and as * such you may alter it freely. * * @param key The configuration key. * @return The associated List object. * @throws ClassCastException is thrown if the key maps to an * object that is not a List. * @since Commons Collections 3.2 */ public List getList(String key) { return getList(key, null); } /** * Get a List of strings associated with the given configuration key. * <p> * The list is a copy of the internal data of this object, and as * such you may alter it freely. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated List. * @throws ClassCastException is thrown if the key maps to an * object that is not a List. * @since Commons Collections 3.2 */ public List getList(String key, List defaultValue) { Object value = get(key); if (value instanceof List) { return new ArrayList((List) value); } else if (value instanceof String) { List values = new ArrayList(1); values.add(value); put(key, values); return values; } else if (value == null) { if (defaults != null) { return defaults.getList(key, defaultValue); } else { return ((defaultValue == null) ? new ArrayList() : defaultValue); } } else { throw new ClassCastException('\'' + key + "' doesn't map to a List object"); } } /** * Get a boolean associated with the given configuration key. * * @param key The configuration key. * @return The associated boolean. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Boolean. */ public boolean getBoolean(String key) { Boolean b = getBoolean(key, null); if (b != null) { return b.booleanValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a boolean associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated boolean. * @throws ClassCastException is thrown if the key maps to an * object that is not a Boolean. */ public boolean getBoolean(String key, boolean defaultValue) { return getBoolean(key, new Boolean(defaultValue)).booleanValue(); } /** * Get a boolean associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated boolean if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Boolean. */ public Boolean getBoolean(String key, Boolean defaultValue) { Object value = get(key); if (value instanceof Boolean) { return (Boolean) value; } else if (value instanceof String) { String s = testBoolean((String) value); Boolean b = new Boolean(s); put(key, b); return b; } else if (value == null) { if (defaults != null) { return defaults.getBoolean(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Boolean object"); } } /** * Test whether the string represent by value maps to a boolean * value or not. We will allow <code>true</code>, <code>on</code>, * and <code>yes</code> for a <code>true</code> boolean value, and * <code>false</code>, <code>off</code>, and <code>no</code> for * <code>false</code> boolean values. Case of value to test for * boolean status is ignored. * * @param value the value to test for boolean state * @return <code>true</code> or <code>false</code> if the supplied * text maps to a boolean value, or <code>null</code> otherwise. */ public String testBoolean(String value) { String s = value.toLowerCase(); if (s.equals("true") || s.equals("on") || s.equals("yes")) { return "true"; } else if (s.equals("false") || s.equals("off") || s.equals("no")) { return "false"; } else { return null; } } /** * Get a byte associated with the given configuration key. * * @param key The configuration key. * @return The associated byte. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Byte. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public byte getByte(String key) { Byte b = getByte(key, null); if (b != null) { return b.byteValue(); } else { throw new NoSuchElementException('\'' + key + " doesn't map to an existing object"); } } /** * Get a byte associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated byte. * @throws ClassCastException is thrown if the key maps to an * object that is not a Byte. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public byte getByte(String key, byte defaultValue) { return getByte(key, new Byte(defaultValue)).byteValue(); } /** * Get a byte associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated byte if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Byte. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Byte getByte(String key, Byte defaultValue) { Object value = get(key); if (value instanceof Byte) { return (Byte) value; } else if (value instanceof String) { Byte b = new Byte((String) value); put(key, b); return b; } else if (value == null) { if (defaults != null) { return defaults.getByte(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Byte object"); } } /** * Get a short associated with the given configuration key. * * @param key The configuration key. * @return The associated short. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Short. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public short getShort(String key) { Short s = getShort(key, null); if (s != null) { return s.shortValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a short associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated short. * @throws ClassCastException is thrown if the key maps to an * object that is not a Short. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public short getShort(String key, short defaultValue) { return getShort(key, new Short(defaultValue)).shortValue(); } /** * Get a short associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated short if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Short. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Short getShort(String key, Short defaultValue) { Object value = get(key); if (value instanceof Short) { return (Short) value; } else if (value instanceof String) { Short s = new Short((String) value); put(key, s); return s; } else if (value == null) { if (defaults != null) { return defaults.getShort(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Short object"); } } /** * The purpose of this method is to get the configuration resource * with the given name as an integer. * * @param name The resource name. * @return The value of the resource as an integer. */ public int getInt(String name) { return getInteger(name); } /** * The purpose of this method is to get the configuration resource * with the given name as an integer, or a default value. * * @param name The resource name * @param def The default value of the resource. * @return The value of the resource as an integer. */ public int getInt(String name, int def) { return getInteger(name, def); } /** * Get a int associated with the given configuration key. * * @param key The configuration key. * @return The associated int. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Integer. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public int getInteger(String key) { Integer i = getInteger(key, null); if (i != null) { return i.intValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a int associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated int. * @throws ClassCastException is thrown if the key maps to an * object that is not a Integer. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public int getInteger(String key, int defaultValue) { Integer i = getInteger(key, null); if (i == null) { return defaultValue; } return i.intValue(); } /** * Get a int associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated int if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Integer. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Integer getInteger(String key, Integer defaultValue) { Object value = get(key); if (value instanceof Integer) { return (Integer) value; } else if (value instanceof String) { Integer i = new Integer((String) value); put(key, i); return i; } else if (value == null) { if (defaults != null) { return defaults.getInteger(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Integer object"); } } /** * Get a long associated with the given configuration key. * * @param key The configuration key. * @return The associated long. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Long. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public long getLong(String key) { Long l = getLong(key, null); if (l != null) { return l.longValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a long associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated long. * @throws ClassCastException is thrown if the key maps to an * object that is not a Long. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public long getLong(String key, long defaultValue) { return getLong(key, new Long(defaultValue)).longValue(); } /** * Get a long associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated long if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Long. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Long getLong(String key, Long defaultValue) { Object value = get(key); if (value instanceof Long) { return (Long) value; } else if (value instanceof String) { Long l = new Long((String) value); put(key, l); return l; } else if (value == null) { if (defaults != null) { return defaults.getLong(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Long object"); } } /** * Get a float associated with the given configuration key. * * @param key The configuration key. * @return The associated float. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Float. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public float getFloat(String key) { Float f = getFloat(key, null); if (f != null) { return f.floatValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a float associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated float. * @throws ClassCastException is thrown if the key maps to an * object that is not a Float. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public float getFloat(String key, float defaultValue) { return getFloat(key, new Float(defaultValue)).floatValue(); } /** * Get a float associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated float if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Float. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Float getFloat(String key, Float defaultValue) { Object value = get(key); if (value instanceof Float) { return (Float) value; } else if (value instanceof String) { Float f = new Float((String) value); put(key, f); return f; } else if (value == null) { if (defaults != null) { return defaults.getFloat(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Float object"); } } /** * Get a double associated with the given configuration key. * * @param key The configuration key. * @return The associated double. * @throws NoSuchElementException is thrown if the key doesn't * map to an existing object. * @throws ClassCastException is thrown if the key maps to an * object that is not a Double. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public double getDouble(String key) { Double d = getDouble(key, null); if (d != null) { return d.doubleValue(); } else { throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); } } /** * Get a double associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated double. * @throws ClassCastException is thrown if the key maps to an * object that is not a Double. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public double getDouble(String key, double defaultValue) { return getDouble(key, new Double(defaultValue)).doubleValue(); } /** * Get a double associated with the given configuration key. * * @param key The configuration key. * @param defaultValue The default value. * @return The associated double if key is found and has valid * format, default value otherwise. * @throws ClassCastException is thrown if the key maps to an * object that is not a Double. * @throws NumberFormatException is thrown if the value mapped * by the key has not a valid number format. */ public Double getDouble(String key, Double defaultValue) { Object value = get(key); if (value instanceof Double) { return (Double) value; } else if (value instanceof String) { Double d = new Double((String) value); put(key, d); return d; } else if (value == null) { if (defaults != null) { return defaults.getDouble(key, defaultValue); } else { return defaultValue; } } else { throw new ClassCastException('\'' + key + "' doesn't map to a Double object"); } } /** * Convert a standard properties class into a configuration class. * <p> * NOTE: From Commons Collections 3.2 this method will pick up * any default parent Properties of the specified input object. * * @param props the properties object to convert * @return new ExtendedProperties created from props */ public static ExtendedProperties convertProperties(Properties props) { ExtendedProperties c = new ExtendedProperties(); for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String s = (String) e.nextElement(); c.setProperty(s, props.getProperty(s)); } return c; } }