Config.java

/*
 * Config
 */
package gov.usgs.util;

import java.util.Properties;
import java.util.Map;
import java.util.HashMap;

import java.lang.ref.WeakReference;

/**
 * The configuration object used for distribution.
 *
 * This object holds a singleton reference to the global configuration. Users
 * should use the static method getConfig() to retrieve the loaded
 * configuration.
 *
 * Objects are loaded from Config objects using the property "type". The value
 * of the property "type" should be a fully qualified class name. This can be
 * used for Any type of object that has a constructor with no arguments. If the
 * loaded object implements the Configurable interface, its "configure" method
 * is called using the Config object.
 *
 * <pre>
 * [objectName]
 * type = fully.qualified.Classname
 * ... other object properties
 *
 * [anotherObject]
 * ...
 * </pre>
 *
 * Some objects refer to other named configured objects. These named objects
 * correspond to sections in the global configuration object. These objects are
 * loaded using the getObject() method:
 *
 * <pre>
 * Classname obj = (Classname) Config.getConfig().getObject(&quot;objectName&quot;);
 * </pre>
 *
 * or
 *
 * <pre>
 * Classname obj = (Classname) new Config(Config.getConfig().getSection(
 * 		&quot;objectName&quot;)).getObject();
 * </pre>
 *
 */
public class Config extends Ini {

	/** Serialization version number. */
	private static final long serialVersionUID = 1L;

	/** Property name that specifies the object type. */
	public static final String OBJECT_TYPE_PROPERTY = "type";

	/** Map from short configuration types to fully qualified class names. */
	public static final Map<String, String> OBJECT_TYPE_MAP = new HashMap<String, String>();

	/** Singleton configuration object. */
	private static Config SINGLETON = null;

	/**
	 * Weak references to already loaded objects. WeakReferences are cleared
	 * more aggressively than SoftReferences, the idea is finalize as soon as no
	 * one else holds a reference.
	 */
	private Map<String, WeakReference<Object>> loadedObjects = new HashMap<String, WeakReference<Object>>();

	/**
	 * Create an empty Config object.
	 */
	public Config() {
		super();
	}

	/**
	 * Create a Config object using properties for defaults.
	 *
	 * @param properties
	 *            the default properties.
	 */
	public Config(Properties properties) {
		super(properties);
	}

	/**
	 * Load a configured object.
	 *
	 * Uses the property "type" to determine which type of object to load. If
	 * the loaded object implements the Configurable interface, its configure
	 * method is called before returning.
	 *
	 * @return the loaded object
	 * @throws Exception
	 *             if any errors occur.
	 */
	public Object getObject() throws Exception {
		String className = getProperty(OBJECT_TYPE_PROPERTY);
		// must specify type
		if (className == null) {
			return null;
		}

		// config defines shortnames that can be used for common classes
		if (OBJECT_TYPE_MAP.containsKey(className)) {
			className = OBJECT_TYPE_MAP.get(className);
		}

		Object obj = Class.forName(className)
				.getConstructor().newInstance();
		if (obj instanceof Configurable) {
			((Configurable) obj).configure(this);
		}
		return obj;
	}

	/**
	 * Get a section as a Config object.
	 *
	 * @param name
	 *            name of config section.
	 * @return section properties as a Config object.
	 * @throws Exception
	 *             if any errors occur.
	 */
	public Object getObject(final String name) throws Exception {
		Object object = null;

		// check if already loaded
		WeakReference<Object> ref = loadedObjects.get(name);
		if (ref != null) {
			// already loaded, if not still in memory this returns null
			object = ref.get();
		}

		if (object == null) {
			// not loaded yet
			Properties props = getSections().get(name);
			if (props != null) {
				object = new Config(props).getObject();
				if (object instanceof Configurable) {
					((Configurable)object).setName(name);
				}
				loadedObjects.put(name, new WeakReference<Object>(object));
			}
		}

		return object;
	}

	/**
	 * Get the global configuration object.
	 *
	 * Users are encouraged to not maintain a reference to the returned object,
	 * in case it is updated.
	 *
	 * @return the global configuration object.
	 */
	public static Config getConfig() {
		return SINGLETON;
	}

	/**
	 * Set the global configuration object.
	 *
	 * @param config
	 *            the new global configuration object.
	 */
	public static void setConfig(final Config config) {
		SINGLETON = config;
	}

}