Widening.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@2536 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
samskivert
2009-03-13 22:26:47 +00:00
parent 5266497ece
commit fdc60e29c6
+118 -177
View File
@@ -24,24 +24,20 @@ import java.io.*;
import java.net.URL; import java.net.URL;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.util.*; import java.util.*;
import java.util.Map;
import static com.samskivert.Log.log; import static com.samskivert.Log.log;
/** /**
* The config util class provides routines for loading configuration * The config util class provides routines for loading configuration information.
* information.
*
* @see #loadProperties
* @see #getSystemProperty
*/ */
public class ConfigUtil public class ConfigUtil
{ {
/** /**
* Obtains the specified system property via {@link * Obtains the specified system property via {@link System#getProperty}, parses it into an
* System#getProperty}, parses it into an integer and returns the * integer and returns the value. If the property is not set, the default value will be
* value. If the property is not set, the default value will be * returned. If the property is not a properly formatted integer, an error will be logged and
* returned. If the property is not a properly formatted integer, an * the default value will be returned.
* error will be logged and the default value will be returned.
*/ */
public static int getSystemProperty (String key, int defval) public static int getSystemProperty (String key, int defval)
{ {
@@ -59,20 +55,17 @@ public class ConfigUtil
} }
/** /**
* Loads a properties file from the named file that exists somewhere * Loads a properties file from the named file that exists somewhere in the classpath.
* in the classpath.
* *
* <p> The classloader that loaded the <code>ConfigUtil</code> class * <p> The classloader that loaded the <code>ConfigUtil</code> class is searched first,
* is searched first, followed by the system classpath. If you wish to * followed by the system classpath. If you wish to provide an additional classloader, use the
* provide an additional classloader, use the version of this function * version of this function that takes a classloader as an argument.
* that takes a classloader as an argument.
* *
* @param path The path to the properties file, relative to the root * @param path The path to the properties file, relative to the root of the classpath entry
* of the classpath entry from which it will be loaded * from which it will be loaded (e.g. <code>com/foo/config.properties</code>).
* (e.g. <code>com/foo/config.properties</code>).
* *
* @return A properties object loaded with the contents of the * @return A properties object loaded with the contents of the specified file if the file could
* specified file if the file could be found, null otherwise. * be found, null otherwise.
*/ */
public static Properties loadProperties (String path) public static Properties loadProperties (String path)
throws IOException throws IOException
@@ -81,9 +74,8 @@ public class ConfigUtil
} }
/** /**
* Like {@link #loadProperties(String)} but this method uses the * Like {@link #loadProperties(String)} but this method uses the supplied class loader rather
* supplied class loader rather than the class loader used to load the * than the class loader used to load the <code>ConfigUtil</code> class.
* <code>ConfigUtil</code> class.
*/ */
public static Properties loadProperties (String path, ClassLoader loader) public static Properties loadProperties (String path, ClassLoader loader)
throws IOException throws IOException
@@ -94,8 +86,8 @@ public class ConfigUtil
} }
/** /**
* Like {@link #loadProperties(String,ClassLoader)} but the properties * Like {@link #loadProperties(String,ClassLoader)} but the properties are loaded into the
* are loaded into the supplied {@link Properties} object. * supplied {@link Properties} object.
*/ */
public static void loadProperties ( public static void loadProperties (
String path, ClassLoader loader, Properties target) String path, ClassLoader loader, Properties target)
@@ -110,18 +102,16 @@ public class ConfigUtil
} }
/** /**
* Creates a properties instance by combining properties files loaded * Creates a properties instance by combining properties files loaded using the specified
* using the specified classpath-relative property file * classpath-relative property file path.
* path.
* *
* <p> The inheritance works in two ways: * <p> The inheritance works in two ways:
* *
* <ul><li><b>Overrides</b> * <ul><li><b>Overrides</b>
* *
* <p> All properties files with the specified name are located * <p> All properties files with the specified name are located in the classpath and merged
* in the classpath and merged into a single set of properties * into a single set of properties according to an explicit inheritance hierarchy defined by a
* according to an explicit inheritance hierarchy defined by a couple * couple of custom properties. This is best explained with an example:
* of custom properties. This is best explained with an example:
* *
* <p><code>com/samskivert/foolib/config.properties</code> contains: * <p><code>com/samskivert/foolib/config.properties</code> contains:
* *
@@ -133,10 +123,9 @@ public class ConfigUtil
* ... * ...
* </pre> * </pre>
* *
* and this is packaged into <code>foolib.jar</code>. Happy Corp * and this is packaged into <code>foolib.jar</code>. Happy Corp writes an application that
* writes an application that uses foolib and wants to override some * uses foolib and wants to override some properties in foolib's configuration. They create a
* properties in foolib's configuration. They create a properties file * properties file in <code>happyapp.jar</code> with the path
* in <code>happyapp.jar</code> with the path
* <code>com/samskivert/foolib/config.properties</code>. It contains: * <code>com/samskivert/foolib/config.properties</code>. It contains:
* *
* <pre> * <pre>
@@ -146,15 +135,13 @@ public class ConfigUtil
* config2 = happyvalue * config2 = happyvalue
* </pre> * </pre>
* *
* When foolib loads its <code>config.properties</code> the overrides * When foolib loads its <code>config.properties</code> the overrides from
* from <code>happyapp.jar</code> will be applied to the properties * <code>happyapp.jar</code> will be applied to the properties defined in
* defined in <code>foolib.jar</code> and foolib will see Happy Corp's * <code>foolib.jar</code> and foolib will see Happy Corp's overridden properties.
* overridden properties.
* *
* <p> Note that conflicting overrides must be resolved "by hand" so * <p> Note that conflicting overrides must be resolved "by hand" so to speak. For example, if
* to speak. For example, if Happy Corp used some other library that * Happy Corp used some other library that also overrode foolib's configuration, say barlib,
* also overrode foolib's configuration, say barlib, whose * whose <code>barlib.jar</code> contained:
* <code>barlib.jar</code> contained:
* *
* <pre> * <pre>
* _package = barlib * _package = barlib
@@ -163,10 +150,9 @@ public class ConfigUtil
* config1 = barvalue * config1 = barvalue
* </pre> * </pre>
* *
* Happy Corp's <code>config.properties</code> would not be able to * Happy Corp's <code>config.properties</code> would not be able to override foolib's
* override foolib's configuration directly because the config system * configuration directly because the config system would not know which overrides to use. It
* would not know which overrides to use. It instead must override * instead must override barlib's configuration, like so:
* barlib's configuration, like so:
* *
* <pre> * <pre>
* _package = happyapp * _package = happyapp
@@ -174,9 +160,8 @@ public class ConfigUtil
* ... * ...
* </pre> * </pre>
* *
* Moreover, if there were yet a third library that also overrode * Moreover, if there were yet a third library that also overrode foolib, Happy Corp would have
* foolib, Happy Corp would have to resolve the conflict between * to resolve the conflict between barlib and bazlib explicitly:
* barlib and bazlib explicitly:
* *
* <pre> * <pre>
* _package = happyapp * _package = happyapp
@@ -184,20 +169,17 @@ public class ConfigUtil
* ... * ...
* </pre> * </pre>
* *
* This would force bazlib's overrides to take precedence over * This would force bazlib's overrides to take precedence over barlib's overrides, resolving
* barlib's overrides, resolving the inheritance ambiguity created * the inheritance ambiguity created when both barlib and bazlib claimed to override foolib.
* when both barlib and bazlib claimed to override foolib.
* *
* <p> This all certainly seems a bit complicated, but in most cases * <p> This all certainly seems a bit complicated, but in most cases there is only one user of
* there is only one user of a library and overriding is very * a library and overriding is very straightforward. The additional functionality is provided
* straightforward. The additional functionality is provided to * to resolve cases where conflicts do arise.
* resolve cases where conflicts do arise.
* *
* <li><b>Extends</b> * <li><b>Extends</b>
* *
* <p> The second type of inheritance, extension, is more * <p> The second type of inheritance, extension, is more straightforward. In this case, a
* straightforward. In this case, a properties file explicitly extends * properties file explicitly extends another properties file. For example,
* another properties file. For example,
* <code>com/foocorp/puzzle/config.properties</code> contains: * <code>com/foocorp/puzzle/config.properties</code> contains:
* *
* <pre> * <pre>
@@ -216,55 +198,45 @@ public class ConfigUtil
* bonus = 55 * bonus = 55
* </pre> * </pre>
* *
* The Footris configuration will inherit default values from the * The Footris configuration will inherit default values from the general puzzle configuration,
* general puzzle configuration, overriding any that are specified * overriding any that are specified explicitly.
* explicitly.
* *
* <p> When resolving a properties file that extends another * <p> When resolving a properties file that extends another properties file, first the
* properties file, first the extended properties are loaded and all * extended properties are loaded and all <code>_overrides</code> are applied to that
* <code>_overrides</code> are applied to that properties file. Then * properties file. Then all <code>_overrides</code> are applied to the extending properties
* all <code>_overrides</code> are applied to the extending properties * file and then the extending properties are merged with the extended properties. </ul>
* file and then the extending properties are merged with the extended
* properties.
* </ul>
* *
* <em>A final note:</em> All of the inheritance directives must be * <em>A final note:</em> All of the inheritance directives must be grouped together in an
* grouped together in an uninterrupted sequence of lines. One the * uninterrupted sequence of lines. One the parsing code finds the first directive, it stops
* parsing code finds the first directive, it stops parsing when it * parsing when it sees a line that does not contain a directive.
* sees a line that does not contain a directive.
* *
* @param path The path to the properties file, relative to the root * @param path The path to the properties file, relative to the root of the classpath entries
* of the classpath entries from which it will be loaded * from which it will be loaded (e.g. <code>com/foo/config.properties</code>).
* (e.g. <code>com/foo/config.properties</code>).
* *
* @return A properties object loaded with the contents of the * @return A properties object loaded with the contents of the specified file if the file could
* specified file if the file could be found, null otherwise. * be found, null otherwise.
*/ */
public static Properties loadInheritedProperties (String path) public static Properties loadInheritedProperties (String path)
throws IOException throws IOException
{ {
return loadInheritedProperties( return loadInheritedProperties(path, ConfigUtil.class.getClassLoader());
path, ConfigUtil.class.getClassLoader());
} }
/** /**
* Like {@link #loadInheritedProperties(String)} but loads the * Like {@link #loadInheritedProperties(String)} but loads the properties into the supplied
* properties into the supplied target object. * target object.
*/ */
public static void loadInheritedProperties (String path, Properties target) public static void loadInheritedProperties (String path, Properties target)
throws IOException throws IOException
{ {
loadInheritedProperties( loadInheritedProperties(path, ConfigUtil.class.getClassLoader(), target);
path, ConfigUtil.class.getClassLoader(), target);
} }
/** /**
* Like {@link #loadInheritedProperties(String)} but this method uses * Like {@link #loadInheritedProperties(String)} but this method uses the supplied class loader
* the supplied class loader rather than the class loader used to load * rather than the class loader used to load the <code>ConfigUtil</code> class.
* the <code>ConfigUtil</code> class.
*/ */
public static Properties loadInheritedProperties ( public static Properties loadInheritedProperties (String path, ClassLoader loader)
String path, ClassLoader loader)
throws IOException throws IOException
{ {
Properties props = new Properties(); Properties props = new Properties();
@@ -273,21 +245,18 @@ public class ConfigUtil
} }
/** /**
* Like {@link #loadInheritedProperties(String,ClassLoader)} but the * Like {@link #loadInheritedProperties(String,ClassLoader)} but the properties are loaded into
* properties are loaded into the supplied properties object. * the supplied properties object. Properties that already exist in the supplied object will
* Properties that already exist in the supplied object will be * be overwritten by the loaded properties where they have the same key.
* overwritten by the loaded properties where they have the same key.
*/ */
public static void loadInheritedProperties ( public static void loadInheritedProperties (String path, ClassLoader loader, Properties target)
String path, ClassLoader loader, Properties target)
throws IOException throws IOException
{ {
// first look for the files in the supplied class loader // first look for the files in the supplied class loader
Enumeration<URL> enm = getResources(path, loader); Enumeration<URL> enm = getResources(path, loader);
if (!enm.hasMoreElements()) { if (!enm.hasMoreElements()) {
// if we couldn't find anything there, try the system class // if we couldn't find anything there, try the system class loader (but only if that's
// loader (but only if that's not where we were already // not where we were already looking)
// looking)
try { try {
ClassLoader sysloader = ClassLoader.getSystemClassLoader(); ClassLoader sysloader = ClassLoader.getSystemClassLoader();
if (sysloader != loader) { if (sysloader != loader) {
@@ -304,19 +273,16 @@ public class ConfigUtil
rsrcs.add(enm.nextElement()); rsrcs.add(enm.nextElement());
} }
// if we found no resources in our enumerations, try loading the // if we found no resources in our enumerations, try loading the properties using
// properties using getResource() rather than getResources(); this // getResource() rather than getResources(); this works around a bug in Tomcat 3.3's
// works around a bug in Tomcat 3.3's classloader wherein it // classloader wherein it simply returns nothing to all calls to getResources()
// simply returns nothing to all calls to getResources()
if (rsrcs.size() == 0) { if (rsrcs.size() == 0) {
// if getStream() returns something, but getResources() // if getStream() returns something, but getResources() returned nothing, then we know
// returned nothing, then we know there's a bug and we do our // there's a bug and we do our best to work around it
// best to work around it
InputStream in = getStream(path, loader); InputStream in = getStream(path, loader);
if (in != null) { if (in != null) {
log.warning("Buggy classloader: getResources() returned " + log.warning("Buggy classloader: getResources() returned no resources, but " +
"no resources, but getResourceAsStream() " + "getResourceAsStream() returned a resource [path=" + path +
"returned a resource [path=" + path +
", loader=" + StringUtil.shortClassName(loader) + ", loader=" + StringUtil.shortClassName(loader) +
"]. Using the one we could get our hands on."); "]. Using the one we could get our hands on.");
target.load(in); target.load(in);
@@ -339,8 +305,8 @@ public class ConfigUtil
// parse the metadata for this properties file // parse the metadata for this properties file
PropRecord record = parseMetaData(path, rsrcs.get(ii)); PropRecord record = parseMetaData(path, rsrcs.get(ii));
// make sure the record we parseded is valid because we're // make sure the record we parseded is valid because we're going to be doing
// going to be doing overrides // overrides
record.validate(); record.validate();
// then map it according to its package defintion // then map it according to its package defintion
@@ -350,9 +316,8 @@ public class ConfigUtil
if (record._overrides == null) { if (record._overrides == null) {
// make sure there aren't two or more roots // make sure there aren't two or more roots
if (root != null) { if (root != null) {
String errmsg = record + " cannot have the same " + String errmsg = record + " cannot have the same path as " + root +
"path as " + root + " without one overriding " + " without one overriding the other.";
"the other.";
throw new IOException(errmsg); throw new IOException(errmsg);
} }
root = record; root = record;
@@ -364,9 +329,8 @@ public class ConfigUtil
if (prec._overrides == null) { if (prec._overrides == null) {
// sanity check // sanity check
if (prec != root) { if (prec != root) {
String errmsg = "Internal error: found multiple " + String errmsg = "Internal error: found multiple roots where we shouldn't " +
"roots where we shouldn't have [root=" + root + "have [root=" + root + ", prec=" + prec + "]";
", prec=" + prec + "]";
throw new IOException(errmsg); throw new IOException(errmsg);
} }
continue; continue;
@@ -377,8 +341,7 @@ public class ConfigUtil
String ppkg = prec._overrides[ii]; String ppkg = prec._overrides[ii];
PropRecord parent = map.get(ppkg); PropRecord parent = map.get(ppkg);
if (parent == null) { if (parent == null) {
throw new IOException("Cannot find parent '" + ppkg + throw new IOException("Cannot find parent '" + ppkg + "' for " + prec);
"' for " + prec);
} }
parent.add(prec); parent.add(prec);
} }
@@ -393,9 +356,8 @@ public class ConfigUtil
} }
if (crowns.size() == 0) { if (crowns.size() == 0) {
String errmsg = "No top-level property override could be " + String errmsg = "No top-level property override could be found, perhaps there " +
"found, perhaps there are circular references " + "are circular references " + StringUtil.toString(map.values());
StringUtil.toString(map.values());
throw new IOException(errmsg); throw new IOException(errmsg);
} else if (crowns.size() > 1) { } else if (crowns.size() > 1) {
@@ -420,16 +382,14 @@ public class ConfigUtil
try { try {
loadInheritedProperties(root._extends, loader, target); loadInheritedProperties(root._extends, loader, target);
} catch (FileNotFoundException fnfe) { } catch (FileNotFoundException fnfe) {
String errmsg = "Unable to locate parent '" + root._extends + throw new IOException(
"' for '" + root.path + "'"; "Unable to locate parent '" + root._extends + "' for '" + root.path + "'");
throw new IOException(errmsg);
} }
} }
// now apply all of the overrides, in the appropriate order // now apply all of the overrides, in the appropriate order
if (crown != null) { if (crown != null) {
HashMap<String,PropRecord> applied = HashMap<String,PropRecord> applied = new HashMap<String,PropRecord>();
new HashMap<String,PropRecord>();
loadPropertiesOverrides(crown, map, applied, loader, target); loadPropertiesOverrides(crown, map, applied, loader, target);
} else { } else {
@@ -445,12 +405,10 @@ public class ConfigUtil
target.remove(OVERRIDES_KEY); target.remove(OVERRIDES_KEY);
} }
/** {@link #loadInheritedProperties(String,ClassLoader,Properties)} /** {@link #loadInheritedProperties(String,ClassLoader,Properties)} helper function. */
* helper function. */
protected static void loadPropertiesOverrides ( protected static void loadPropertiesOverrides (
PropRecord prec, HashMap<String,PropRecord> records, PropRecord prec, Map<String,PropRecord> records, Map<String,PropRecord> applied,
HashMap<String,PropRecord> applied, ClassLoader loader, ClassLoader loader, Properties target)
Properties target)
throws IOException throws IOException
{ {
if (applied.containsKey(prec._package)) { if (applied.containsKey(prec._package)) {
@@ -461,8 +419,7 @@ public class ConfigUtil
if (prec._overrides != null) { if (prec._overrides != null) {
for (int ii = 0; ii < prec._overrides.length; ii++) { for (int ii = 0; ii < prec._overrides.length; ii++) {
PropRecord parent = records.get(prec._overrides[ii]); PropRecord parent = records.get(prec._overrides[ii]);
loadPropertiesOverrides( loadPropertiesOverrides(parent, records, applied, loader, target);
parent, records, applied, loader, target);
} }
} }
@@ -474,8 +431,8 @@ public class ConfigUtil
} }
/** /**
* Performs simple processing of the supplied input stream to obtain * Performs simple processing of the supplied input stream to obtain inheritance metadata from
* inheritance metadata from the properties file. * the properties file.
*/ */
protected static PropRecord parseMetaData (String path, URL sourceURL) protected static PropRecord parseMetaData (String path, URL sourceURL)
throws IOException throws IOException
@@ -528,17 +485,14 @@ public class ConfigUtil
} }
/** /**
* Returns an input stream referencing a file that exists somewhere in * Returns an input stream referencing a file that exists somewhere in the classpath.
* the classpath.
* *
* <p> The classloader that loaded the <code>ConfigUtil</code> class * <p> The classloader that loaded the <code>ConfigUtil</code> class is searched first,
* is searched first, followed by the system classpath. If you wish to * followed by the system classpath. If you wish to provide an additional classloader, use the
* provide an additional classloader, use the version of this function * version of this function that takes a classloader as an argument.
* that takes a classloader as an argument.
* *
* @param path The path to the file, relative to the root of the * @param path The path to the file, relative to the root of the classpath directory from which
* classpath directory from which it will be loaded * it will be loaded (e.g. <code>com/foo/bar/foo.gif</code>).
* (e.g. <code>com/foo/bar/foo.gif</code>).
*/ */
public static InputStream getStream (String path) public static InputStream getStream (String path)
{ {
@@ -546,15 +500,12 @@ public class ConfigUtil
} }
/** /**
* Returns an input stream referencing a file that exists somewhere in * Returns an input stream referencing a file that exists somewhere in the classpath.
* the classpath.
* *
* <p> The supplied classloader is searched first, followed by the * <p> The supplied classloader is searched first, followed by the system classloader.
* system classloader.
* *
* @param path The path to the file, relative to the root of the * @param path The path to the file, relative to the root of the classpath directory from which
* classpath directory from which it will be loaded * it will be loaded (e.g. <code>com/foo/bar/foo.gif</code>).
* (e.g. <code>com/foo/bar/foo.gif</code>).
*/ */
public static InputStream getStream (String path, ClassLoader loader) public static InputStream getStream (String path, ClassLoader loader)
{ {
@@ -564,8 +515,8 @@ public class ConfigUtil
return in; return in;
} }
// if that didn't work, try the system class loader (but only if // if that didn't work, try the system class loader (but only if it's different from the
// it's different from the class loader we just tried) // class loader we just tried)
try { try {
ClassLoader sysloader = ClassLoader.getSystemClassLoader(); ClassLoader sysloader = ClassLoader.getSystemClassLoader();
if (sysloader != loader) { if (sysloader != loader) {
@@ -577,8 +528,7 @@ public class ConfigUtil
return null; return null;
} }
protected static InputStream getResourceAsStream ( protected static InputStream getResourceAsStream (String path, ClassLoader loader)
String path, ClassLoader loader)
{ {
// make sure the class loader isn't null // make sure the class loader isn't null
if (loader == null) { if (loader == null) {
@@ -595,8 +545,7 @@ public class ConfigUtil
return loader.getResourceAsStream(togglePath(path)); return loader.getResourceAsStream(togglePath(path));
} }
protected static Enumeration<URL> getResources ( protected static Enumeration<URL> getResources (String path, ClassLoader loader)
String path, ClassLoader loader)
throws IOException throws IOException
{ {
// make sure the class loader isn't null // make sure the class loader isn't null
@@ -633,38 +582,30 @@ public class ConfigUtil
public String path; public String path;
public URL sourceURL; public URL sourceURL;
public PropRecord (String path, URL sourceURL) public PropRecord (String path, URL sourceURL) {
{
this.path = path; this.path = path;
this.sourceURL = sourceURL; this.sourceURL = sourceURL;
} }
public void validate () public void validate () throws IOException {
throws IOException
{
if (StringUtil.isBlank(_package)) { if (StringUtil.isBlank(_package)) {
throw new IOException( throw new IOException("Properties file missing '_package' identifier " + this);
"Properties file missing '_package' identifier " + this);
} }
if ((_overrides != null && _overrides.length > 0) && if ((_overrides != null && _overrides.length > 0) &&
(!StringUtil.isBlank(_extends))) { (!StringUtil.isBlank(_extends))) {
throw new IOException("Properties file cannot use both " + throw new IOException(
"'_overrides' and '_extends' " + this); "Properties file cannot use both '_overrides' and '_extends' " + this);
} }
} }
@Override public boolean equals (Object other) @Override public boolean equals (Object other) {
{
return _package.equals(((PropRecord)other)._package); return _package.equals(((PropRecord)other)._package);
} }
@Override public String toString () @Override public String toString () {
{ return "[package=" + _package + ", overrides=" + StringUtil.toString(_overrides) +
return "[package=" + _package + ", extends=" + _extends + ", path=" + path + ", source=" + sourceURL + "]";
", overrides=" + StringUtil.toString(_overrides) +
", extends=" + _extends + ", path=" + path +
", source=" + sourceURL + "]";
} }
} }