From 7f38a5536b105a093526f86f1af5e2c875658f0a Mon Sep 17 00:00:00 2001 From: mdb Date: Wed, 8 Aug 2001 23:46:00 +0000 Subject: [PATCH] Added the ability to inherit up the classpath when loading a properties file. This allows one to stick a properties file earlier in the classpath that will override values in the properties file (with the same path) that shows up later in the classpath. git-svn-id: https://samskivert.googlecode.com/svn/trunk@222 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../src/java/com/samskivert/util/Config.java | 21 +- .../java/com/samskivert/util/ConfigUtil.java | 192 ++++++++++++++---- 2 files changed, 176 insertions(+), 37 deletions(-) diff --git a/projects/samskivert/src/java/com/samskivert/util/Config.java b/projects/samskivert/src/java/com/samskivert/util/Config.java index 6fb7fe79..f8d4168b 100644 --- a/projects/samskivert/src/java/com/samskivert/util/Config.java +++ b/projects/samskivert/src/java/com/samskivert/util/Config.java @@ -1,5 +1,5 @@ // -// $Id: Config.java,v 1.3 2001/07/21 00:50:28 shaper Exp $ +// $Id: Config.java,v 1.4 2001/08/08 23:46:00 mdb Exp $ package com.samskivert.util; @@ -43,11 +43,14 @@ public class Config * somewhere in the classpath. For example: foo/bar/baz * would indicate a file named "foo/bar/baz.properties" living in the * 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) + public void bindProperties (String name, String path, boolean inherit) throws IOException { // append the file suffix onto the path @@ -57,10 +60,22 @@ public class Config if (props == null) { throw new IOException("Unable to load properties file: " + path); } - // put it into the hashtable with the specified name + // bind the properties instance _props.put(name, props); } + /** + * A backwards compatibility method that does not use inherited + * properties loading. + * + * @see #bindProperties(String,String,boolean) + */ + public void bindProperties (String name, String path) + throws IOException + { + bindProperties(name, path, false); + } + /** * Fetches and returns the value for the specified configuration * property. If the value is not specified in the associated diff --git a/projects/samskivert/src/java/com/samskivert/util/ConfigUtil.java b/projects/samskivert/src/java/com/samskivert/util/ConfigUtil.java index 78b545d5..5e242ec2 100644 --- a/projects/samskivert/src/java/com/samskivert/util/ConfigUtil.java +++ b/projects/samskivert/src/java/com/samskivert/util/ConfigUtil.java @@ -1,12 +1,17 @@ // -// $Id: ConfigUtil.java,v 1.2 2001/02/15 01:15:02 mdb Exp $ +// $Id: ConfigUtil.java,v 1.3 2001/08/08 23:46:00 mdb Exp $ package com.samskivert.util; import java.io.InputStream; import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; import java.util.Properties; +import com.samskivert.Log; + /** * The config util class provides routines for loading configuration * information out of a file that lives somewhere in the classpath. @@ -26,7 +31,7 @@ public class ConfigUtil * * @param path The path to the properties file, relative to the root * of the classpath entry from which it will be loaded - * (e.g. /conf/foo.properties or perhaps just bar.properties). + * (e.g. /conf/foo.properties). * * @return A properties object loaded with the contents of the * specified file if the file could be found, null otherwise. @@ -38,25 +43,16 @@ public class ConfigUtil } /** - * Loads a properties file from the named file that exists somewhere - * in the classpath. A full path should be supplied, but variations - * including and not including a leading slash will be used because - * JVMs differ on their opinion of whether this is necessary. + * Like the other version of {@link #loadProperties(String)} + * but this one uses the supplied class loader rather than the class + * loader used to load the ConfigUtil class. * - *

The supplied classloader is searched first, followed by the - * system classloader. - * - * @param path The path to the properties file, relative to the root - * of the classpath entry from which it will be loaded - * (e.g. /conf/foo.properties or perhaps just bar.properties). - * - * @return A properties object loaded with the contents of the - * specified file if the file could be found, null otherwise. + * @see #loadProperties(String) */ - public static Properties loadProperties (String name, ClassLoader loader) + public static Properties loadProperties (String path, ClassLoader loader) throws IOException { - InputStream in = getStream(name, loader); + InputStream in = getStream(path, loader); Properties props = null; if (in != null) { @@ -67,6 +63,90 @@ public class ConfigUtil return props; } + /** + * Creates a properties instance by combining properties files loaded + * using the specified classpath-relative property file path. A + * leading slash should be supplied, but variations including and not + * including a leading slash will be used because JVMs differ on their + * opinion of whether this is necessary. + * + *

The inheritance works like so: the file will be searched for in + * the classpath from farthest to nearest. Near and far in the + * classpath are defined by the class loading search order. Normal + * class loading searches from nearest to farthest. Beginning with the + * farthest copy of the properties file, sucessively nearer copies + * will be overlaid onto those properties to achieve a sort of + * inheritance. Properties specified in nearer versions of the file + * will override those in farther versions of the file, but properties + * not specified in nearer versions will be "inherited" from the + * farther versions. Using this mechanism, a standard set of defaults + * can be provided and users need only place a properties file with + * their preferred overrides somewhere nearer in the classpath to have + * those overrides properly combined with the original + * defaults. Because the entire classpath is searched, this process + * can cascade up through a set of properties files and provide a + * powerful mechanism for inheriting configuration information. + * + *

The classloader that loaded the ConfigUtil class + * is searched first, followed by the system classpath. If you wish to + * provide an additional classloader, use the version of this function + * that takes a classloader as an argument. + * + * @param path The path to the properties file, relative to the root + * of the classpath entries from which it will be loaded + * (e.g. /conf/foo.properties). + * + * @return A properties object loaded with the contents of the + * specified file if the file could be found, null otherwise. + */ + public static Properties loadInheritedProperties (String path) + throws IOException + { + return loadInheritedProperties( + path, ConfigUtil.class.getClassLoader()); + } + + /** + * Like the other version of {@link #loadInheritedProperties(String)} + * but this one uses the supplied class loader rather than the class + * loader used to load the ConfigUtil class. + * + * @see #loadInheritedProperties(String) + */ + public static Properties loadInheritedProperties ( + String path, ClassLoader loader) + throws IOException + { + // first look for the files in the supplied class loader + Enumeration enum = getResources(path, loader); + if (!enum.hasMoreElements()) { + // if we couldn't find anything there, try the system class + // loader (but only if that's not where we were already + // looking) + ClassLoader sysloader = ClassLoader.getSystemClassLoader(); + if (sysloader != loader) { + enum = getResources(path, sysloader); + } + } + + // we need to process the resources in reverse order, so we put + // them all into an array first + ArrayList rsrcs = new ArrayList(); + while (enum.hasMoreElements()) { + rsrcs.add(enum.nextElement()); + } + + // now load each file in turn into our properties object + Properties props = new Properties(); + for (int i = rsrcs.size()-1; i >= 0; i--) { + URL rurl = (URL)rsrcs.get(i); + InputStream in = rurl.openStream(); + props.load(in); + } + + return props; + } + /** * Returns an input stream referencing a file that exists somewhere in * the classpath. A full path (relative to the classpath directories) @@ -81,8 +161,8 @@ public class ConfigUtil * * @param path The path to the file, relative to the root of the * classpath directory from which it will be loaded - * (e.g. /conf/foo.gif or perhaps just bar.gif if the file is at the - * top level). + * (e.g. /conf/foo.gif or perhaps just + * /bar.gif if the file is at the top level). */ public static InputStream getStream (String path) { @@ -101,32 +181,62 @@ public class ConfigUtil * * @param path The path to the file, relative to the root of the * classpath directory from which it will be loaded - * (e.g. /conf/foo.gif or perhaps just bar.gif if the file is at the - * top level). + * (e.g. /conf/foo.gif or perhaps just + * /bar.gif if the file is at the top level). */ public static InputStream getStream (String path, ClassLoader loader) { // first try the supplied class loader + InputStream in = getResourceAsStream(path, loader); + if (in != null) { + return in; + } + + // if that didn't work, try the system class loader (but only if + // it's different from the class loader we just tried) + ClassLoader sysloader = ClassLoader.getSystemClassLoader(); + if (sysloader != loader) { + return getResourceAsStream(path, loader); + } else { + return null; + } + } + + protected static InputStream getResourceAsStream ( + String path, ClassLoader loader) + { + // make sure the class loader isn't null + if (loader == null) { +// Log.debug("No loader for get resource request " + +// "[path=" + path + "]."); + return null; + } + // try the path as is InputStream in = loader.getResourceAsStream(path); if (in != null) { return in; } - // try toggling the leading slash - String apath = togglePath(path); - in = loader.getResourceAsStream(apath); - if (in != null) { - return in; - } + return loader.getResourceAsStream(togglePath(path)); + } - // if that didn't work, try the system classloader - in = Class.class.getResourceAsStream(path); - if (in != null) { - return in; + protected static Enumeration getResources ( + String path, ClassLoader loader) + throws IOException + { + // make sure the class loader isn't null + if (loader == null) { +// Log.debug("No loader for get resource request " + +// "[path=" + path + "]."); + return null; + } + // try the path as is + Enumeration enum = loader.getResources(path); + if (enum.hasMoreElements()) { + return enum; } - - // help us obi wan, you're our only hope - return Class.class.getResourceAsStream(apath); + // try toggling the leading slash + return loader.getResources(togglePath(path)); } protected static String togglePath (String path) @@ -137,4 +247,18 @@ public class ConfigUtil return "/" + path; } } + + /** + * Unit test driver. + */ + public static void main (String[] args) + { + try { + String path = "/com/samskivert/util/test.properties"; + Properties props = loadInheritedProperties(path); + System.out.println(props); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } }