/*==========================================================================*\ | $Id: WCProperties.java,v 1.2 2011/03/07 18:44:37 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import com.webobjects.foundation.*; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXValueUtilities; import java.io.*; import java.math.BigDecimal; import java.util.*; import org.webcat.core.WCProperties; import org.apache.log4j.Logger; // ------------------------------------------------------------------------- /** * An enhanced subclass of Properties that self-loads from a file * name and provides more type conversions. Based heavily on * org.w3c.util.ObservableProperties, but without the notification * support. * <p> * This subclass also supports auto-expansion of property references * within property values. References use the form ${key}. Recursive * references are supported up to 256 levels deep. For example, * <p> * <pre> * A = 12345678 * B = ${A}90 * C = ${B} plus more * </pre> * <p> * will result in <code>getProperty("C")</code> * returning the value "1234567890 plus more". The property expansion * support is based on code written by Chris Mair. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.2 $, $Date: 2011/03/07 18:44:37 $ */ public class WCProperties extends java.util.Properties implements NSKeyValueCodingAdditions { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new WCProperties object, initializing its contents from * a property file. * * @param filename The file to load from * @param defaults The defaults */ public WCProperties(String filename, Properties defaults) { super(); this.defaults = defaults; load(filename); } // ---------------------------------------------------------- /** * Creates an empty property list with the specified defaults. * * @param defaults The defaults */ public WCProperties(Properties defaults) { super(); this.defaults = defaults; } // ---------------------------------------------------------- /** * Creates an empty property list with no default values. */ public WCProperties() { super(); } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Load properties from the specified file if it exists. The * full file name of the file will be added to the property * defined by PROPERTYFILES_LOADED if successful. * * @param fileName The property file to load */ public void attemptToLoad(String fileName) { File file = new File(fileName); if ( file.exists() ) { String propertyfiles_loaded = getProperty(PROPERTYFILES_LOADED); try { InputStream is = new FileInputStream(file); log.info( "Loading properties from " + file.getAbsolutePath()); load(is); if (propertyfiles_loaded == null) { propertyfiles_loaded = file.getAbsolutePath(); } else { propertyfiles_loaded += ";" + file.getAbsolutePath(); } setProperty(PROPERTYFILES_LOADED, propertyfiles_loaded); is.close(); } catch (IOException e) { log.error("Error loading properties from " + file.getAbsolutePath() + ":", e); } } } // ---------------------------------------------------------- /** * Loads the contents of the specified file. This function first * looks for the file relative to "Contents/Resources" from the * current directory (where WO "Resources" files are stored). If * found, properties from this file are loaded. Then it looks for * the same file name relative to the user's home directory. If found, * this property file is loaded as well (overriding any previously * defined properties). Finally, it looks relative to the current * directory (which also catches absolute pathnames for the * <code>fileName</code> parameter). If successful, this property * file is loaded last. * * @param fileName The property file to load */ public void load(String fileName) { // First, load from WO subdirectory attemptToLoad("Contents/Resources/" + fileName); // Now, load from user's home directory String dataDir = System.getProperty("user.home"); if(dataDir != null) { if(!dataDir.endsWith(File.separator)) { dataDir += File.separator; } attemptToLoad(dataDir + fileName); } // Finally, load from current dir or absolute path attemptToLoad(fileName); } // ---------------------------------------------------------- /** * Add all properties from the given dictionary to this object. * All keys and values in the dictionary are converted to strings * in order to be added to this property map. * * @param dictionary The map containing properties to add */ public void addPropertiesFromDictionary(NSDictionary<String, ?> dictionary) { if (dictionary == null) { return; } Enumeration<String> e = dictionary.keyEnumerator(); while (e.hasMoreElements()) { String key = e.nextElement(); Object value = dictionary.objectForKey(key); setProperty(key, value.toString()); } } // ---------------------------------------------------------- /** * Add all properties from the given dictionary to this object. * All keys and values in the dictionary are converted to strings * in order to be added to this property map. * * @param dictionary The map containing properties to add */ public void addPropertiesFromDictionaryIfNotDefined( NSDictionary<String, ?> dictionary) { if (dictionary == null) { return; } Enumeration<String> e = dictionary.keyEnumerator(); while (e.hasMoreElements()) { String key = e.nextElement(); if (getProperty(key) == null) { Object value = dictionary.objectForKey(key); setProperty(key, value.toString()); } } } // ---------------------------------------------------------- /** * Get this property value as a float. If the property * does not exist, the default value 0.0 will be returned. * * @param key The name of the property to be fetched * @return Its float value */ public double floatForKey(String key) { String v = getProperty(key); float result = 0.0F; if (v != null) { try { result = Float.parseFloat(v); } catch (NumberFormatException e) { log.error("property " + key + ": '" + v + "' : float format exception"); } } return result; } // ---------------------------------------------------------- /** * Get this property value as a double. If the property * does not exist, the default value 0.0 will be returned. * * @param key The name of the property to be fetched * @return Its double value */ public double doubleForKey(String key) { String v = getProperty(key); double result = 0.0; if (v != null) { try { result = Double.parseDouble(v); } catch (NumberFormatException e) { log.error("property " + key + ": '" + v + "' : double format exception"); } } return result; } // ---------------------------------------------------------- /** * Get this property value as a File. If the property does * not exist, null is returned. * * @param name The name of the property to be fetched * @return A File instance */ public File getFile(String name) { String v = getProperty(name); File result = null; if (v != null) { result = new File(v); } return result; } // ---------------------------------------------------------- /** * Cover method for returning an NSArray for a * given property. * @param s property key * @return array de-serialized from the string in * this properties mapping */ public NSArray<?> arrayForKey(String s) { return arrayForKeyWithDefault(s, null); } // ---------------------------------------------------------- /** * Cover method for returning an NSArray for a * given property and set a default value if not given. * @param s property key * @param defaultValue default value * @return array de-serialized from the string in * the properties or default value */ public NSArray<?> arrayForKeyWithDefault(String s, NSArray<?> defaultValue) { return ERXValueUtilities.arrayValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Cover method for returning a boolean for a * given property. This method uses the * method <code>booleanValue</code> from * {@link ERXValueUtilities}. * @param s property key * @return boolean value of the string in the * properties */ public boolean booleanForKey(String s) { return booleanForKeyWithDefault(s, false); } // ---------------------------------------------------------- /** * Cover method for returning a boolean for a * given property or a default value. This method uses the * method <code>booleanValue</code> from * {@link ERXValueUtilities}. * @param s property key * @param defaultValue default value * @return boolean value of the string in the * properties */ public boolean booleanForKeyWithDefault(String s, boolean defaultValue) { return ERXValueUtilities.booleanValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Cover method for returning an NSDictionary for a * given property. * @param s property key * @return dictionary de-serialized from the string in * the properties */ public NSDictionary<?, ?> dictionaryForKey(String s) { return dictionaryForKeyWithDefault(s, null); } // ---------------------------------------------------------- /** * Cover method for returning an NSDictionary for a * given property or the default value. * @param s property key * @param defaultValue default value * @return dictionary de-serialized from the string in * the properties */ public NSDictionary<?, ?> dictionaryForKeyWithDefault( String s, NSDictionary<?, ?> defaultValue) { return ERXValueUtilities.dictionaryValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Cover method for returning an int for a * given property. * @param s property key * @return int value of the property or 0 */ public int intForKey(String s) { return intForKeyWithDefault(s, 0); } // ---------------------------------------------------------- /** * Cover method for returning a long for a * given property. * @param s property key * @return long value of the property or 0 */ public long longForKey(String s) { return longForKeyWithDefault(s, 0); } // ---------------------------------------------------------- /** * Cover method for returning a BigDecimal for a * given property. This method uses the * method <code>bigDecimalValueWithDefault</code> from * {@link ERXValueUtilities}. * @param s property key * @return bigDecimal value of the string in the properties. Scale is * controlled by the string, ie "4.400" will have a scale of 3. */ public BigDecimal bigDecimalForKey(String s) { return bigDecimalForKeyWithDefault(s, null); } // ---------------------------------------------------------- /** * Cover method for returning a BigDecimal for a * given property or a default value. This method uses the * method <code>bigDecimalValueWithDefault</code> from * {@link ERXValueUtilities}. * @param s property key * @param defaultValue default value * @return BigDecimal value of the string in the properties. Scale is * controlled by the string, ie "4.400" will have a scale of 3. */ public BigDecimal bigDecimalForKeyWithDefault( String s, BigDecimal defaultValue) { return ERXValueUtilities.bigDecimalValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Cover method for returning an int for a * given property with a default value. * @param s property key * @param defaultValue default value * @return int value of the property or the default value */ public int intForKeyWithDefault(String s, int defaultValue) { return ERXValueUtilities.intValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Cover method for returning a long for a * given property with a default value. * @param s property key * @param defaultValue default value * @return long value of the property or the default value */ public long longForKeyWithDefault(String s, long defaultValue) { return ERXValueUtilities.longValueWithDefault( getProperty(s), defaultValue); } // ---------------------------------------------------------- /** * Returning an string for a given * property. This is a cover method of * {@link java.util.Properties#getProperty(String)} * @param s property * @return string value of the property or null */ public String stringForKey(String s) { return stringForKeyWithDefault(s, null); } // ---------------------------------------------------------- /** * Returning an string for a given * property. This is a cover method of * {@link java.util.Properties#getProperty(String,String)} * @param s property key * @param defaultValue default value * @return string value of the property or default value */ public String stringForKeyWithDefault(String s, String defaultValue) { String s1 = getProperty(s); return s1 != null ? s1 : maybeSubstitutePropertyReferences(defaultValue); } // ---------------------------------------------------------- /** * Sets an array in the properties for a particular key. * @param array to be set in the properties * @param key to be used to get the value */ public void setArrayForKey(NSArray<?> array, String key) { setStringForKey( NSPropertyListSerialization.stringFromPropertyList(array, false), key); } // ---------------------------------------------------------- /** * Sets a dictionary in the properties for a particular key. * @param dictionary to be set in the properties * @param key to be used to get the value */ public void setDictionaryForKey(NSDictionary<?, ?> dictionary, String key) { setStringForKey( NSPropertyListSerialization.stringFromPropertyList( dictionary, false), key); } // ---------------------------------------------------------- /** * Sets a string in the properties for another string. * @param string to be set in the properties * @param key to be used to get the value */ public void setStringForKey(String string, String key) { setProperty(key, string); } // ---------------------------------------------------------- /** * Sets an object in the properties file for a particular key, doing an * appropriate string conversion if it is an array or dictionary. * @param obj to be set in the properties * @param key to be used to get the value */ public void setObjectForKey(Object obj, String key) { if (obj instanceof NSArray) { setArrayForKey((NSArray<?>) obj, key); } else if (obj instanceof NSDictionary) { setDictionaryForKey((NSDictionary<?, ?>) obj, key); } else { setStringForKey(obj.toString(), key); } } // ---------------------------------------------------------- /** * Caches the application name for appending to the key. * Note that for a period when the application is starting up * application() will be null and name() will be null. * @return application name used for appending, for example ".ERMailer" */ protected String applicationNameForAppending() { if (applicationNameForAppending == null) { applicationNameForAppending = com.webobjects.appserver.WOApplication.application() != null ? com.webobjects.appserver.WOApplication.application().name() : null; if (applicationNameForAppending != null) { applicationNameForAppending = "." + applicationNameForAppending; } } return applicationNameForAppending; } // ---------------------------------------------------------- /** * Substitutes property references in the given string. Any reference * of the form ${key} is processed by looking up "key" and replacing * the entire reference with the key's value. * <p> * If "key" is not defined in this property map (or any of its * ancestors), then the reference is left unchanged. * <p> * If the value for "key" contains other property references, they are * also expanded (up to a maximum recursive depth of 256, which is just * to prevent stack overflow). If this recursive depth is exceeded while * expanding property references, the property reference is replaced * by the string "${key:RECURSION-TO-DEEP}". * @param value the string to perform substitution on * @return The value, with all property references substituted */ public String substitutePropertyReferences(String value) { return substitutePropertyReferences(value, MAX_RECURSIVE_DEPTH); } // ---------------------------------------------------------- /** * Just like {@link #substitutePropertyReferences(String)}, but * only performs substitution if * {@link #willPerformPropertySubstitution()}. * @param value the string to perform substitution on * @return The value, with all property references substituted */ public String maybeSubstitutePropertyReferences(String value) { if (willPerformPropertySubstitution) { return substitutePropertyReferences(value, MAX_RECURSIVE_DEPTH); } else { return value; } } // ---------------------------------------------------------- /** * Performs the real substitution work for * {@link #substitutePropertyReferences(String)}. Based on code * published by Chris Mair. * @param value the string to perform substitution on * @param maxDepth the limit on the number of levels of recursion that * can be used in substituting properties * @return The value, with all property references substituted */ private String substitutePropertyReferences(String value, int maxDepth) { if (value == null || value.length() == 0) { return value; } // Get the index of the first constant, if any StringBuffer buffer = new StringBuffer(value.length()); int beginIndex = 0; int startName = value.indexOf(REFERENCE_START, beginIndex); while (startName >= 0) { int endName = value.indexOf(REFERENCE_END, startName); if (endName == -1) { // Terminating symbol not found // Return the value as is break; } if (startName > beginIndex) { buffer.append(value.substring(beginIndex, startName)); beginIndex = startName; } String constName = value.substring(startName + REFERENCE_START.length(), endName); String constValue = (maxDepth > 0) ? getProperty(constName, true, maxDepth - 1) : REFERENCE_START + constName + ":RECURSION-TOO-DEEP" + REFERENCE_END; if (constValue == null) { // Property name not found buffer.append(value.substring(beginIndex, endName + REFERENCE_END.length())); } else { // Insert the constant value into the // original property value buffer.append(constValue); } beginIndex = endName + REFERENCE_END.length(); // Look for the next constant startName = value.indexOf(REFERENCE_START, beginIndex); } buffer.append(value.substring(beginIndex, value.length())); return buffer.toString(); } // ---------------------------------------------------------- /** * Overriding the default setProperty method to check for and handle * the NO_SUBSTITUTION_PREFIX on the key. * @param key to check * @return property value */ public synchronized Object setProperty(String key, String value) { if (key != null && key.startsWith(NO_SUBSTITUTION_PREFIX)) { key = key.substring(NO_SUBSTITUTION_PREFIX.length()); } return super.setProperty(key, value); } // ---------------------------------------------------------- /** * Overriding the default getProperty method to first check: * key.<ApplicationName> before checking for key. If nothing * is found then key. Default is checked. * @param key to check * @return property value */ public String getProperty(String key) { return getProperty( key, willPerformPropertySubstitution, MAX_RECURSIVE_DEPTH); } // ---------------------------------------------------------- /** * Overriding the default getProperty method to first check: * key.<ApplicationName> before checking for key. If nothing * is found then key. Default is checked. * @param key to check * @param performSubstitution if true, any property references within * the resulting value will be expanded through substitution * @param maxDepth the limit on the number of levels of recursion that * can be used in substituting properties * @return property value */ public String getProperty( String key, boolean performSubstitution, int maxDepth) { String property = null; String application = applicationNameForAppending(); if (key != null && key.startsWith(NO_SUBSTITUTION_PREFIX)) { key = key.substring(NO_SUBSTITUTION_PREFIX.length()); performSubstitution = false; } if (application != null) { property = super.getProperty(key + application); } if (property == null) { property = super.getProperty(key); if (property == null) { property = super.getProperty(key + ERXProperties.DefaultString); } // This behavior from ERXProperties makes it hard to dynamically // reset properties easily, so turn it off here. // // We go ahead and set the value to increase the lookup the // // next time the property is accessed. // if (property != null && application != null) // { // setProperty(key + application, property); // } } if (performSubstitution) { property = substitutePropertyReferences(property, maxDepth); } return property; } // ---------------------------------------------------------- /** * Attempts to coerce the string value of a property into its "actual" * type, based on what it looks like. If it starts with a parenthesis, * we try it as an array. If it starts with a brace, we try it as a * dictionary. Otherwise, we try it as a number, and then fall back to * the string itself if these fail. * * @param property the string value of the property to coerce * @param numericsOnly if true, attempt only the numeric and boolean * conversions, leaving out the array/dictionary ones. This is used * when recursively coercing the values in a dictionary or array * @return the coerced value of the property */ private Object tryToCoercePropertyValue( String property, boolean numericsOnly) { Object coercedValue = null; if (!numericsOnly && property.startsWith("(")) { // Try to parse the value as an array try { coercedValue = ERXValueUtilities.arrayValue(property); } catch (IllegalArgumentException e) { coercedValue = null; } if (coercedValue != null) { // If we got an array, recursively perform conversions of // values that look like numbers or booleans. coercedValue = recursivelyCoerceArrayValues( (NSArray<?>) coercedValue); } } if (!numericsOnly && coercedValue == null && property.startsWith("{")) { // Try to parse the value as a dictionary try { coercedValue = ERXValueUtilities.dictionaryValue(property); } catch (IllegalArgumentException e) { coercedValue = null; } if (coercedValue != null) { // If we got a dictionary, recursively perform conversions of // values that look like numbers or booleans. coercedValue = recursivelyCoerceDictionaryValues( (NSDictionary<?, ?>) coercedValue); } } if ("true".equalsIgnoreCase(property) || "false".equalsIgnoreCase(property)) { // Parse the value as a boolean. coercedValue = Boolean.valueOf(property); } if (coercedValue == null) { // Try to parse it as a number (integer, long, then floating point) coercedValue = tryToParseAsNumber(property); } if (coercedValue == null) { // All coercions failed, just keep the string coercedValue = property; } return coercedValue; } // ------------------------------------------------ /** * Walks through an array (and recursively through any nested arrays or * dictionaries) and converts any string values that look like numbers * into their actual numeric type. * * @param array the array of objects to convert * @return a new array containing the converted items */ private NSArray<?> recursivelyCoerceArrayValues(NSArray<?> array) { if (array == null) { return null; } else { NSMutableArray<?> newArray = new NSMutableArray<Object>(); for (Object value : array) { Object newValue = null; if (value instanceof String) { newValue = tryToCoercePropertyValue((String) value, true); if (newValue == null) { newValue = value; } } else if (value instanceof NSArray) { newValue = recursivelyCoerceArrayValues((NSArray<?>)value); } else if (value instanceof NSDictionary) { newValue = recursivelyCoerceDictionaryValues( (NSDictionary<?, ?>)value); } else { newValue = value; } if (newValue != null) { newArray.addObject(newValue); } } return newArray; } } // ------------------------------------------------ /** * Walks through a dictionary (and recursively through any nested arrays * or dictionaries) and converts any string values that look like * numbers into their actual numeric type. * * @param dictionary the dictionary of objects to convert * @return a new dictionary containing the converted items */ private NSDictionary<?, ?> recursivelyCoerceDictionaryValues( NSDictionary<?, ?> dictionary) { if (dictionary == null) { return null; } else { NSMutableDictionary<?, ?> newDictionary = new NSMutableDictionary<Object, Object>(); for (Object key : dictionary.allKeys()) { Object value = dictionary.objectForKey(key); Object newValue = null; if (value instanceof String) { newValue = tryToCoercePropertyValue((String)value, true); if (newValue == null) { newValue = value; } } else if (value instanceof NSArray) { newValue = recursivelyCoerceArrayValues((NSArray<?>)value); } else if (value instanceof NSDictionary) { newValue = recursivelyCoerceDictionaryValues( (NSDictionary<?, ?>)value); } else { newValue = value; } if (newValue != null) { newDictionary.setObjectForKey(newValue, key); } } return newDictionary; } } // ---------------------------------------------------------- /** * Tries to parse the specified string as a number; try this in order * of "ascending precision", more or less: Integer, Long, Double, and * finally BigDecimal. * * @param stringValue the string to try to parse as a number * @return if successful, a Number object representing the numerical * value of the string; otherwise, null */ private Number tryToParseAsNumber(String stringValue) { Number number = null; try { number = Integer.valueOf(stringValue); } catch (NumberFormatException e) { // Do nothing, number is still null. } try { number = Integer.valueOf(stringValue); } catch (NumberFormatException e) { // Do nothing, number is still null. } if (number == null) { try { number = Long.valueOf(stringValue); } catch (NumberFormatException e) { // Do nothing, number is still null. } } if (number == null) { try { number = Double.valueOf(stringValue); } catch (NumberFormatException e) { // Do nothing, number is still null. } } if (number == null) { try { number = new BigDecimal(stringValue); } catch (NumberFormatException e) { // Do nothing, number is still null. } } return number; } // ---------------------------------------------------------- /** * Sets the receiver's value for the property identified by key * to value. If the value is null, the property is removed * from the current property object. This will <b>not</b> * remove inherited default values. * * @param value The value to set * @param key The name of the property to set */ public void takeValueForKey(Object value, String key) { if (value == null || value == NSKeyValueCoding.NullValue) { remove(key); } else { setProperty(key, value.toString()); } } // ---------------------------------------------------------- /** * Sets the receiver's value for the property identified by key path * to value. If the key path exists as a distinct key in the dictionary, * this method set the corresponding object in the dictionary by invoking * takeValueForKey. Otherwise, it recursively invokes valueForKey for * each segment of the keyPath on the result of the previous segment, * and then takeValueForKey on the last object in the path. * * @param value The value to set * @param keyPath The path to the property to set */ public void takeValueForKeyPath(Object value, String keyPath) { log.debug("takeValueForKeyPath(" + keyPath + ")"); if (getProperty( keyPath ) != null) { takeValueForKey(value, keyPath); } else { int pos = keyPath.indexOf(KeyPathSeparator); if (pos < 0) { takeValueForKey(value, keyPath); } else { String prefix = keyPath.substring(0, pos); if (getProperty(prefix) != null) { NSKeyValueCodingAdditions.DefaultImplementation .takeValueForKeyPath(this, value, keyPath); } else { takeValueForKey(value, keyPath); } } } } // ---------------------------------------------------------- /** * Returns the value bound to the given key. * * @param key The name of the property to fetch * @return Its value, or null if there is no binding */ public Object valueForKey(String key) { String result = getProperty(key); if (result != null) { return tryToCoercePropertyValue(result, false); } else { // AJA 2008-10-31: if the property name starts with "raw." and it's // not a property in its own right, then chop off the "raw." part // and get the property with that name as its literal string value. if (key.startsWith("raw.")) { key = key.substring(4); result = getProperty(key); if (result != null) { return result; } } return null; } } // ---------------------------------------------------------- /** * Returns the value bound to the given key path. * If the key path exists as a distinct key in the dictionary, * this method returns the corresponding object in the dictionary by * invoking valueForKey. Otherwise, it recursively invokes valueForKey * for each segment of the keyPath on the result of the previous segment. * * @param keyPath The path to the property to fetch * @return Its value, or null if there is no binding */ public Object valueForKeyPath(String keyPath) { log.debug("valueForKeyPath(" + keyPath + ")"); String result = getProperty(keyPath); if (result != null) { return tryToCoercePropertyValue(result, false); } else { // AJA 2008-10-31: if the property name starts with "raw." and it's // not a property in its own right, then chop off the "raw." part // and get the property with that name as its literal string value. if (keyPath.startsWith("raw.")) { keyPath = keyPath.substring(4); result = getProperty(keyPath); if (result != null) { return result; } } return NSKeyValueCodingAdditions.DefaultImplementation .valueForKeyPath(this, keyPath); } } // ---------------------------------------------------------- /** * Returns the set of locally defined properties. * @return the set of locally defined properties * @see java.util.Map.Entry */ public Set<Map.Entry<Object, Object>> localEntrySet() { return entrySet(); } // ---------------------------------------------------------- /** * Returns the set of default-defined properties. * @return the set of default-defined properties * @see java.util.Map.Entry */ public Set<Map.Entry<Object, Object>> inheritedEntrySet() { return defaults.entrySet(); } // ---------------------------------------------------------- /** * Find out if property references (in ${property} form) within * property values will be substituted when properties are retrieved. * @return true if substitution will be performed */ public boolean willPerformPropertySubstitution() { return willPerformPropertySubstitution; } // ---------------------------------------------------------- /** * Determine if property references (in ${property} form) within * property values will be substituted when properties are retrieved. * @param value true if substitution is desired, false if it should be * turned off */ public void setWillPerformPropertySubstitution(boolean value) { willPerformPropertySubstitution = value; } //~ Instance/static variables ............................................. /** caches the application name that is appended to the key for lookup */ protected String applicationNameForAppending; protected boolean willPerformPropertySubstitution = true; private final String REFERENCE_START = "${"; private final String REFERENCE_END = "}"; private final int MAX_RECURSIVE_DEPTH = 256; static Logger log = Logger.getLogger(WCProperties.class); public static final String PROPERTYFILES_LOADED = "propertyfiles.loaded"; public static final String NO_SUBSTITUTION_PREFIX = "NOSUB."; }