Revamped the way configuration and preferences work. Now there is a config

object associated with each different package's preferences (rather than
everything being bound into one object and differentiated by passing a
prefix) and the base config system supports user overrides of
configuration information (preferences) using the Java preferences
services.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@685 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2002-03-28 21:50:27 +00:00
parent 2c659068f7
commit 752a14a5e3
2 changed files with 200 additions and 263 deletions
@@ -1,5 +1,5 @@
// //
// $Id: Config.java,v 1.9 2002/03/28 18:57:34 mdb Exp $ // $Id: Config.java,v 1.10 2002/03/28 21:50:26 mdb Exp $
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001 Michael Bayne // Copyright (C) 2001 Michael Bayne
@@ -23,78 +23,75 @@ package com.samskivert.util;
import java.io.IOException; import java.io.IOException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator; import java.util.Iterator;
import java.util.Properties; import java.util.Properties;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import com.samskivert.Log; import com.samskivert.Log;
/** /**
* The config class provides a unified interaface to application * The config class provides a unified interaface to application
* configuration information. It takes care of loading properties files * configuration information. It takes care of loading properties files
* from locations in the classpath and binding the properties in those * (done via the classpath) and merging configuration data from multiple
* files into the global config namespace. It also provides access to more * configuration files with the same path (so that users of packages can
* datatypes than simply strings, handling the parsing of ints as well as * override configuration settings for the packages that they use).
* int arrays and string arrays.
* *
* <p> An application should construct a single instance of * <p> The primary pattern is to create, for each package that shares
* <code>Config</code> and use it to access all of its configuration * configuration information, a singleton class containing a config object
* information. * that is configured to load its data from a single configuration
* file. For example:
*
* <pre>
* public class FooConfig
* {
* public static Config config = new Config("com/fribitz/foo");
* }
* </pre>
*
* which would look for <code>com/fribitz/foo.properties</code> in the
* classpath and serve up those configuration values when requests were
* made from <code>FooConfig.config</code>.
*
* <p> The config class allows for users to override configuration values
* persistently, using the standard Java {@link Preferences} facilities to
* maintain the overridden values. If a value is set in a configuration
* object, it will remain overridden in between invocations of the
* application (and generally leverage the benefits of the pluggable
* preferences backends provided by the standard preferences stuff).
*/ */
public class Config public class Config
{ {
/** /**
* Constructs a new config object which can be used immediately by * Constructs a new config object which will obtain configuration
* binding properties files into the namespace and subsequently * information from the specified properties bundle.
* requesting values.
*/ */
public Config () public Config (String path)
{ {
} // first load up our default prefs
try {
// append the file suffix onto the path
String ppath = path + PROPS_SUFFIX;
/** // load the properties file
* Binds the specified properties file into the namespace with the _props = ConfigUtil.loadProperties(ppath);
* specified name. If the properties file in question contains a if (_props == null) {
* property of the name <code>foo.bar</code> and the file is bound Log.warning("Unable to locate configuration definitions " +
* into the namespace under <code>baz</code>, then that property would "[path=" + path + "].");
* be accessed as <code>baz.foo.bar</code>. _props = new Properties();
* }
* @param name the root name for all properties in this file.
* @param path the path to the properties file which must live } catch (IOException ioe) {
* somewhere in the classpath. For example: <code>foo/bar/baz</code> Log.warning("Unable to load configuration [path=" + path +
* would indicate a file named "foo/bar/baz.properties" living in the ", ioe=" + ioe + "].");
* classpath.
* @param inherit if true, the properties file will be loaded using
* {@link ConfigUtil#loadInheritedProperties} rather than {@link
* ConfigUtil#loadProperties}.
*
* @exception IOException thrown if an error occurrs loading the
* properties file (like it doesn't exist or cannot be accessed).
*/
public void bindProperties (String name, String path, boolean inherit)
throws IOException
{
// append the file suffix onto the path
path += PROPS_SUFFIX;
// load the properties file
Properties props = ConfigUtil.loadProperties(path);
if (props == null) {
throw new IOException("Unable to load properties file: " + path);
} }
// bind the properties instance
_props.put(name, props);
}
/** // get a handle on the preferences instance that we'll use to
* A backwards compatibility method that does not use inherited // override values in the properties file
* properties loading. _prefs = Preferences.userRoot().node(path);
*
* @see #bindProperties(String,String,boolean)
*/
public void bindProperties (String name, String path)
throws IOException
{
bindProperties(name, path, false);
} }
/** /**
@@ -105,9 +102,7 @@ public class Config
* integer, not in proper array specification), a warning message will * integer, not in proper array specification), a warning message will
* be logged and the default value will be returned. * be logged and the default value will be returned.
* *
* @param the fully qualified name of the property (fully qualified * @param name name of the property.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defval the value to return if the property is not specified * @param defval the value to return if the property is not specified
* in the config file. * in the config file.
* *
@@ -115,21 +110,28 @@ public class Config
*/ */
public int getValue (String name, int defval) public int getValue (String name, int defval)
{ {
String val = resolveProperty(name); // if there is a value, parse it into an integer
String val = _props.getProperty(name);
// if it's not specified, we return the default if (val != null) {
if (val == null) { try {
return defval; defval = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
Log.warning("Malformed integer property [name=" + name +
", value=" + val + "].");
}
} }
// otherwise parse it into an integer // finally look for an overridden value
try { return _prefs.getInt(name, defval);
return Integer.parseInt(val); }
} catch (NumberFormatException nfe) {
Log.warning("Malformed integer property [fqn=" + name + /**
", value=" + val + "]."); * Sets the value of the specified preference, overriding the value
return defval; * defined in the configuration files shipped with the application.
} */
public void setValue (String name, int value)
{
_prefs.putInt(name, value);
} }
/** /**
@@ -137,9 +139,7 @@ public class Config
* property. If the value is not specified in the associated * property. If the value is not specified in the associated
* properties file, the supplied default value is returned instead. * properties file, the supplied default value is returned instead.
* *
* @param the fully qualified name of the property (fully qualified * @param name the name of the property to be fetched.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defval the value to return if the property is not specified * @param defval the value to return if the property is not specified
* in the config file. * in the config file.
* *
@@ -147,9 +147,23 @@ public class Config
*/ */
public String getValue (String name, String defval) public String getValue (String name, String defval)
{ {
String val = resolveProperty(name); // if there is a value, parse it into an integer
// if it's not specified, we return the default String val = _props.getProperty(name);
return (val == null) ? defval : val; if (val != null) {
defval = val;
}
// finally look for an overridden value
return _prefs.get(name, defval);
}
/**
* Sets the value of the specified preference, overriding the value
* defined in the configuration files shipped with the application.
*/
public void setValue (String name, String value)
{
_prefs.put(name, value);
} }
/** /**
@@ -160,9 +174,7 @@ public class Config
* config value is <code>"false"</code> (case-insensitive), else * config value is <code>"false"</code> (case-insensitive), else
* the return value will be true. * the return value will be true.
* *
* @param the fully qualified name of the property (fully qualified * @param name the name of the property to be fetched.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defval the value to return if the property is not specified * @param defval the value to return if the property is not specified
* in the config file. * in the config file.
* *
@@ -170,9 +182,23 @@ public class Config
*/ */
public boolean getValue (String name, boolean defval) public boolean getValue (String name, boolean defval)
{ {
String val = resolveProperty(name); // if there is a value, parse it into an integer
// if it's not specified, we return the default String val = _props.getProperty(name);
return (val == null) ? defval : !val.equalsIgnoreCase("false"); if (val != null) {
defval = !val.equalsIgnoreCase("false");
}
// finally look for an overridden value
return _prefs.getBoolean(name, defval);
}
/**
* Sets the value of the specified preference, overriding the value
* defined in the configuration files shipped with the application.
*/
public void setValue (String name, boolean value)
{
_prefs.putBoolean(name, value);
} }
/** /**
@@ -183,9 +209,7 @@ public class Config
* integer, not in proper array specification), a warning message will * integer, not in proper array specification), a warning message will
* be logged and the default value will be returned. * be logged and the default value will be returned.
* *
* @param the fully qualified name of the property (fully qualified * @param name the name of the property to be fetched.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defval the value to return if the property is not specified * @param defval the value to return if the property is not specified
* in the config file. * in the config file.
* *
@@ -193,24 +217,33 @@ public class Config
*/ */
public int[] getValue (String name, int[] defval) public int[] getValue (String name, int[] defval)
{ {
String val = resolveProperty(name); // look up the value in the configuration file and use that to
// look up any overridden value
String val = _prefs.get(name, _props.getProperty(name));
int[] result = defval;
// if it's not specified, we return the default // parse it into an array of ints
if (val == null) { if (val != null) {
return defval; result = StringUtil.parseIntArray(val);
} if (result == null) {
Log.warning("Malformed int array property [name=" + name +
// otherwise parse it into an array of ints ", value=" + val + "].");
int[] result = StringUtil.parseIntArray(val); return defval;
if (result == null) { }
Log.warning("Malformed int array property [fqn=" + name +
", value=" + val + "].");
return defval;
} }
return result; return result;
} }
/**
* Sets the value of the specified preference, overriding the value
* defined in the configuration files shipped with the application.
*/
public void setValue (String name, int[] value)
{
_prefs.put(name, StringUtil.toString(value, "", ""));
}
/** /**
* Fetches and returns the value for the specified configuration * Fetches and returns the value for the specified configuration
* property. If the value is not specified in the associated * property. If the value is not specified in the associated
@@ -219,9 +252,7 @@ public class Config
* integer, not in proper array specification), a warning message will * integer, not in proper array specification), a warning message will
* be logged and the default value will be returned. * be logged and the default value will be returned.
* *
* @param key the fully qualified name of the property (fully qualified * @param name the name of the property to be fetched.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defval the value to return if the property is not specified * @param defval the value to return if the property is not specified
* in the config file. * in the config file.
* *
@@ -229,37 +260,44 @@ public class Config
*/ */
public String[] getValue (String name, String[] defval) public String[] getValue (String name, String[] defval)
{ {
String val = resolveProperty(name); // look up the value in the configuration file and use that to
// look up any overridden value
String val = _prefs.get(name, _props.getProperty(name));
String[] result = defval;
// if it's not specified, we return the default // parse it into an array of ints
if (val == null) { if (val != null) {
return defval; result = StringUtil.parseStringArray(val);
} if (result == null) {
Log.warning("Malformed string array property [name=" + name +
// otherwise parse it into an array of strings ", value=" + val + "].");
String[] result = StringUtil.parseStringArray(val); return defval;
if (result == null) { }
Log.warning("Malformed string array property [fqn=" + name +
", value=" + val + "].");
return defval;
} }
return result; return result;
} }
/** /**
* Looks up the specified string-valued configuration entry, * Sets the value of the specified preference, overriding the value
* loads the class with that name and instantiates a new instance * defined in the configuration files shipped with the application.
* of that class, which is returned. */
public void setValue (String name, String[] value)
{
_prefs.put(name, StringUtil.joinEscaped(value));
}
/**
* Looks up the specified string-valued configuration entry, loads the
* class with that name and instantiates a new instance of that class,
* which is returned.
* *
* @param key the fully qualified name of the property (fully qualified * @param name the name of the property to be fetched.
* meaning that it contains the namespace identifier as well), for
* example: <code>foo.bar.baz</code>.
* @param defcname the class name to use if the property is not * @param defcname the class name to use if the property is not
* specified in the config file. * specified in the config file.
* *
* @exception Exception thrown if any error occurs while loading * @exception Exception thrown if any error occurs while loading or
* or instantiating the class. * instantiating the class.
*/ */
public Object instantiateValue (String name, String defcname) public Object instantiateValue (String name, String defcname)
throws Exception throws Exception
@@ -268,125 +306,41 @@ public class Config
} }
/** /**
* Returns the entire properties instance bound to a particular * Returns an iterator that returns all of the configuration keys in
* namespace identifier. * this config object.
*
* @return a properties instance that was bound to the specified
* namespace identifier or null if nothing is bound to that
* identifier.
*/ */
public Properties getProperties (String name) public Iterator keys ()
{ {
return (Properties)_props.get(name); // what with all the complicated business, we just need to take
} // the brute force approach and enumerate everything up front
HashSet matches = new HashSet();
/** // add the keys provided in the config files
* Returns an iterator that returns all of the configuration keys that Enumeration defkeys = _props.propertyNames();
* match the specified prefix. The prefix should at least contain a while (defkeys.hasMoreElements()) {
* namespace identifier but can contain further path components to matches.add(defkeys.nextElement());
* restrict the iteration. For example: <code>foo</code> would iterate
* over every property key in the properties file that was bound to
* <code>foo</code>. <code>foo.bar</code> would iterate over every
* property key in the <code>foo</code> property file that began with
* the string <code>bar</code>.
*
* <p> If an invalid or non-existent namespace identifier is supplied,
* a warning will be logged and an empty iterator.
*/
public Iterator keys (String prefix)
{
String id = prefix;
String key = "";
// parse the key prefix if one was provided
int didx = prefix.indexOf(".");
if (didx != -1) {
id = prefix.substring(0, didx);
key = prefix.substring(didx+1);
} }
Properties props = (Properties)_props.get(id); // then add the overridden keys
if (props == null) { try {
Log.warning("No property file bound to top-level name " + String[] keys = _prefs.keys();
"[name=" + id + ", key=" + key + "]."); for (int i = 0; i < keys.length; i++) {
return new Iterator() { matches.add(keys[i]);
public boolean hasNext () { return false; }
public Object next () { return null; }
public void remove () { /* do nothing */ };
};
}
return new PropertyIterator(key, props.keys());
}
protected static class PropertyIterator implements Iterator
{
public PropertyIterator (String prefix, Enumeration enum)
{
_prefix = prefix;
_enum = enum;
scanToNext();
}
public boolean hasNext ()
{
return (_next != null);
}
public Object next ()
{
String next = _next;
scanToNext();
return next;
}
public void remove ()
{
// not supported
}
protected void scanToNext ()
{
// assume that nothing is left
_next = null;
while (_enum.hasMoreElements()) {
String next = (String)_enum.nextElement();
if (next.startsWith(_prefix)) {
_next = next;
break;
}
} }
} catch (BackingStoreException bse) {
Log.warning("Unable to enumerate preferences keys " +
"[error=" + bse + "].");
} }
protected String _prefix; return matches.iterator();
protected Enumeration _enum;
protected String _next;
} }
protected String resolveProperty (String name) /** Contains the default configuration information. */
{ protected Properties _props;
int didx = name.indexOf(".");
if (didx == -1) {
Log.warning("Invalid fully qualified property name " +
"[name=" + name + "].");
return null;
}
String id = name.substring(0, didx); /** Used to maintain configuration overrides. */
String key = name.substring(didx+1); protected Preferences _prefs;
Properties props = (Properties)_props.get(id);
if (props == null) {
Log.warning("No property file bound to top-level name " +
"[name=" + id + ", key=" + key + "].");
return null;
}
return props.getProperty(key);
}
protected Hashtable _props = new Hashtable();
/** The file extension used for configuration files. */
protected static final String PROPS_SUFFIX = ".properties"; protected static final String PROPS_SUFFIX = ".properties";
} }
@@ -1,10 +1,8 @@
// //
// $Id: ConfigTest.java,v 1.1 2002/03/28 18:57:34 mdb Exp $ // $Id: ConfigTest.java,v 1.2 2002/03/28 21:50:27 mdb Exp $
package com.samskivert.util; package com.samskivert.util;
import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import junit.framework.Test; import junit.framework.Test;
@@ -22,44 +20,29 @@ public class ConfigTest extends TestCase
public void runTest () public void runTest ()
{ {
Config config = new Config(); Config config = new Config("rsrc/util/test");
try {
config.bindProperties("test", "rsrc/util/test");
System.out.println("test.prop1: " + System.out.println("prop1: " + config.getValue("prop1", 1));
config.getValue("test.prop1", 1)); System.out.println("prop2: " + config.getValue("prop2", "two"));
System.out.println("test.prop2: " +
config.getValue("test.prop2", "two"));
int[] ival = new int[] { 1, 2, 3 }; int[] ival = new int[] { 1, 2, 3 };
ival = config.getValue("test.prop3", ival); ival = config.getValue("prop3", ival);
System.out.println("test.prop3: " + StringUtil.toString(ival)); System.out.println("prop3: " + StringUtil.toString(ival));
String[] sval = new String[] { "one", "two", "three" }; String[] sval = new String[] { "one", "two", "three" };
sval = config.getValue("test.prop4", sval); sval = config.getValue("prop4", sval);
System.out.println("test.prop4: " + StringUtil.toString(sval)); System.out.println("prop4: " + StringUtil.toString(sval));
System.out.println("test.prop5: " + System.out.println("prop5: " + config.getValue("prop5", "undefined"));
config.getValue("test.prop5", "undefined"));
Iterator iter = config.keys("test.prop2"); // now set some properties
while (iter.hasNext()) { config.setValue("prop1", 15);
System.out.println(iter.next()); System.out.println("prop1: " + config.getValue("prop1", 1));
} config.setValue("prop2", "three");
System.out.println("prop2: " + config.getValue("prop2", "two"));
iter = config.keys("test.prop"); Iterator iter = config.keys();
while (iter.hasNext()) { System.out.println("Keys: " + StringUtil.toString(iter));
System.out.println(iter.next());
}
iter = config.keys("test");
while (iter.hasNext()) {
System.out.println(iter.next());
}
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
}
} }
public static Test suite () public static Test suite ()