package edu.cmu.sphinx.util.props;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;
/**
* Manages a set of <code>Configurable</code>s, their parameterization and the relationships between them. Configurations
* can be specified either by xml or on-the-fly during runtime.
*
* @see edu.cmu.sphinx.util.props.Configurable
* @see edu.cmu.sphinx.util.props.PropertySheet
*/
public class ConfigurationManager implements Cloneable {
private List<ConfigurationChangeListener> changeListeners = new ArrayList<ConfigurationChangeListener>();
private Map<String, PropertySheet> symbolTable = new LinkedHashMap<String, PropertySheet>();
private Map<String, RawPropertyData> rawPropertyMap = new HashMap<String, RawPropertyData>();
private Map<String, String> globalProperties = new HashMap<String, String>();
private boolean showCreations;
private URL configURL;
/**
* Creates a new empty configuration manager. This constructor is only of use in cases when a system configuration
* is created during runtime.
*/
public ConfigurationManager() {
}
/**
* Creates a new configuration manager. Initial properties are loaded from the given URL. No need to keep the notion
* of 'context' around anymore we will just pass around this property manager.
*
* @param configFileName The location of the configuration file.
*/
public ConfigurationManager(String configFileName) throws PropertyException {
this(ConfigurationManagerUtils.getURL(new File(configFileName)));
}
/**
* Creates a new configuration manager. Initial properties are loaded from the given URL. No need to keep the notion
* of 'context' around anymore we will just pass around this property manager.
*
* @param url The location of the configuration file.
*/
public ConfigurationManager(URL url) throws PropertyException {
configURL = url;
try {
rawPropertyMap = new SaxLoader(url, globalProperties).load();
} catch (IOException e) {
throw new RuntimeException(e);
}
ConfigurationManagerUtils.applySystemProperties(rawPropertyMap, globalProperties);
ConfigurationManagerUtils.configureLogger(this);
// we can't configure the configuration manager with itself so we
// do some of these configure items manually.
String showCreations = globalProperties.get("showCreations");
if (showCreations != null)
this.showCreations = "true".equals(showCreations);
}
/**
* Returns the property sheet for the given object instance
*
* @param instanceName the instance name of the object
* @return the property sheet for the object.
*/
public PropertySheet getPropertySheet(String instanceName) {
if (!symbolTable.containsKey(instanceName)) {
// if it is not in the symbol table, so construct
// it based upon our raw property data
RawPropertyData rpd = rawPropertyMap.get(instanceName);
if (rpd != null) {
String className = rpd.getClassName();
try {
Class<?> cls = Class.forName(className);
// now load the property-sheet by using the class annotation
PropertySheet propertySheet = new PropertySheet(cls.asSubclass(Configurable.class), instanceName, this, rpd);
symbolTable.put(instanceName, propertySheet);
} catch (ClassNotFoundException e) {
System.err.println("class not found !" + e);
} catch (ClassCastException e) {
System.err.println("can not cast class !" + e);
} catch (ExceptionInInitializerError e) {
System.err.println("couldn't load class !" + e);
}
}
}
return symbolTable.get(instanceName);
}
/**
* Gets all instances that are of the given type.
*
* @param type the desired type of instance
* @return the set of all instances
*/
public Collection<String> getInstanceNames(Class<? extends Configurable> type) {
Collection<String> instanceNames = new ArrayList<String>();
for (PropertySheet ps : symbolTable.values()) {
if (!ps.isInstanciated())
continue;
if (ConfigurationManagerUtils.isDerivedClass(ps.getConfigurableClass(), type))
instanceNames.add(ps.getInstanceName());
}
return instanceNames;
}
/**
* Returns all names of configurables registered to this instance. The resulting set includes instantiated and
* non-instantiated components.
*
* @return all component named registered to this instance of <code>ConfigurationManager</code>
*/
public Set<String> getComponentNames() {
return rawPropertyMap.keySet();
}
/**
* Looks up a configurable component by name. Creates it if necessary
*
* @param instanceName the name of the component
* @param <C> component type
* @return the component, or null if a component was not found.
* @throws InternalConfigurationException If the requested object could not be properly created, or is not a
* configurable object, or if an error occured while setting a component
* property.
*/
@SuppressWarnings("unchecked")
public <C extends Configurable> C lookup(String instanceName) throws InternalConfigurationException {
// Apply all new properties to the model.
instanceName = getStrippedComponentName(instanceName);
PropertySheet ps = getPropertySheet(instanceName);
if (ps == null)
return null;
if (showCreations)
getRootLogger().config("Creating: " + instanceName);
return (C) ps.getOwner();
}
/**
* Returns a <code>Configurable</code> instance of a given type <code>C</code>, if such a component (or a derived
* one) is registered to this <code>ConfigurationManager</code> instance, and there is one and only match.
* <p>
* This is a convenience method that allows to access a system configuration without knowing the instance names of
* registered components.
*
* @param <C> A component type
* @param confClass class to lookup
* @return The <code>Configurable</code> instance of null if there is no matching <code>Configurable</code>.
* @throws IllegalArgumentException if more than one component of the given type is registered to this
* ConfigurationManager.
*/
public <C extends Configurable> C lookup(Class<C> confClass) {
List<PropertySheet> matchPropSheets = getPropSheets(confClass);
if (matchPropSheets.isEmpty())
return null;
assert matchPropSheets.size() == 1;
return confClass.cast(lookup(matchPropSheets.get(0).getInstanceName()));
}
/**
* Given a <code>Configurable</code>-class/interface, all property-sheets which are subclassing/implemting this
* class/interface are collected and returned. No <code>Configurable</code> will be instantiated by this method.
* @param confClass class to lookup
* @return a list of property sheets
*/
public List<PropertySheet> getPropSheets(Class<? extends Configurable> confClass) {
List<PropertySheet> psCol = new ArrayList<PropertySheet>();
for (PropertySheet ps : symbolTable.values()) {
if (ConfigurationManagerUtils.isDerivedClass(ps.getConfigurableClass(), confClass))
psCol.add(ps);
}
return psCol;
}
/**
* Registers a new configurable to this configuration manager.
*
* @param confClass The class of the configurable to be instantiated and to be added to this configuration manager
* instance.
* @param name The desired lookup-name of the configurable
* @throws IllegalArgumentException if the there's already a component with the same <code>name</code> registered to
* this configuration manager instance.
*/
public void addConfigurable(Class<? extends Configurable> confClass, String name) {
addConfigurable(confClass, name, new HashMap<String, Object>());
}
/**
* Registers a new configurable to this configuration manager.
*
* @param confClass The class of the configurable to be instantiated and to be added to this configuration manager
* instance.
* @param name The desired lookup-name of the configurable
* @param props The properties to be used for component configuration
* @throws IllegalArgumentException if the there's already a component with the same <code>name</code> registered to
* this configuration manager instance.
*/
public void addConfigurable(Class<? extends Configurable> confClass, String name, Map<String, Object> props) {
if (name == null) // use the class name as default if no name is given
name = confClass.getName();
if (symbolTable.containsKey(name))
throw new IllegalArgumentException("tried to override existing component name : " + name);
PropertySheet ps = getPropSheetInstanceFromClass(confClass, props, name, this);
symbolTable.put(name, ps);
rawPropertyMap.put(name, new RawPropertyData(name, confClass.getName()));
for (ConfigurationChangeListener changeListener : changeListeners)
changeListener.componentAdded(this, ps);
}
/**
* Adds an already instantiated <code>Configurable</code> to this configuration manager.
*
* @param configurable A configurable to add
* @param name The desired lookup-instanceName of the configurable
*/
public void addConfigurable(Configurable configurable, String name) {
if (symbolTable.containsKey(name))
throw new IllegalArgumentException("tried to override existing component name");
RawPropertyData dummyRPD = new RawPropertyData(name, configurable.getClass().getName());
PropertySheet ps = new PropertySheet(configurable, name, dummyRPD, this);
symbolTable.put(name, ps);
rawPropertyMap.put(name, dummyRPD);
for (ConfigurationChangeListener changeListener : changeListeners)
changeListener.componentAdded(this, ps);
}
public void renameConfigurable(String oldName, String newName) {
PropertySheet ps = getPropertySheet(oldName);
if (ps == null) {
throw new RuntimeException("no configurable (to be renamed) named " + oldName + " is contained in the CM");
}
ConfigurationManagerUtils.renameComponent(this, oldName, newName);
symbolTable.remove(oldName);
symbolTable.put(newName, ps);
RawPropertyData rpd = rawPropertyMap.remove(oldName);
rawPropertyMap.put(newName, new RawPropertyData(newName, rpd.getClassName(), rpd.getProperties()));
fireRenamedConfigurable(oldName, newName);
}
/** Removes a configurable from this configuration manager.
* @param name a name to remove
*/
public void removeConfigurable(String name) {
assert getComponentNames().contains(name);
PropertySheet ps = symbolTable.remove(name);
rawPropertyMap.remove(name);
for (ConfigurationChangeListener changeListener : changeListeners)
changeListener.componentRemoved(this, ps);
}
/** @param subCM The subconfiguration that should be to this instance */
public void addSubConfiguration(ConfigurationManager subCM) {
addSubConfiguration(subCM, false);
}
/**
* Adds a subconfiguration to this instance by registering all subCM-components and all its global properties.
*
* @param subCM The subconfiguration that should be to this instance
* @param doOverrideComponents If <code>true</code> non-instantiated components will be overridden by elements of
* subCM even if already being registered to this CM-instance. The same holds for global
* properties.
* @throws RuntimeException if an already instantiated component in this instance is redefined in subCM.
*/
public void addSubConfiguration(ConfigurationManager subCM, boolean doOverrideComponents) {
Collection<String> compNames = getComponentNames();
for (String componentName : subCM.getComponentNames()) {
if (compNames.contains(componentName)) {
if (doOverrideComponents && !getPropertySheet(componentName).isInstanciated()) {
PropertySheet ps = subCM.getPropertySheet(componentName);
symbolTable.put(componentName, ps);
rawPropertyMap.put(componentName, new RawPropertyData(componentName, ps.getConfigurableClass().getSimpleName()));
} else {
throw new RuntimeException(componentName + " is already registered to system configuration");
}
}
}
for (String globProp : subCM.globalProperties.keySet()) {
// the second test is necessary because system-props will be global-props in both CMs
if (globalProperties.containsKey(globProp) && !System.getProperties().containsKey(globProp)) {
if (!doOverrideComponents)
throw new RuntimeException(globProp + " is already registered as global property");
}
}
globalProperties.putAll(subCM.globalProperties);
// correct the reference to the configuration manager
for (PropertySheet ps : subCM.symbolTable.values()) {
ps.setCM(this);
}
symbolTable.putAll(subCM.symbolTable);
rawPropertyMap.putAll(subCM.rawPropertyMap);
}
/** @return a copy of the map of global properties set for this configuration manager. */
public Map<String, String> getGlobalProperties() {
return new HashMap<String, String>(globalProperties);
}
/**
* Returns a global property.
*
* @param propertyName The name of the global property or <code>null</code> if no such property exists
* @return a global property
*/
public String getGlobalProperty(String propertyName) {
// propertyName = propertyName.startsWith("$") ? propertyName : "${" + propertyName + "}";
String globProp = globalProperties.get(propertyName);
return globProp != null ? globProp.toString() : null;
}
public String getGloPropReference(String propertyName) {
return globalProperties.get(propertyName);
}
/**
* @return the URL of the XML configuration which defined this configuration or <code>null</code> if it was created
* dynamically.
*/
public URL getConfigURL() {
return configURL;
}
/**
* Sets a global property.
*
* @param propertyName The name of the global property.
* @param value The new value of the global property. If the value is <code>null</code> the property becomes
* removed.
*/
public void setGlobalProperty(String propertyName, String value) {
if (value == null)
globalProperties.remove(propertyName);
else
globalProperties.put(propertyName, value);
// update all component configurations because they might be affected by the change
for (String instanceName : getInstanceNames(Configurable.class)) {
PropertySheet ps = getPropertySheet(instanceName);
if (ps.isInstanciated())
try {
ps.getOwner().newProperties(ps);
} catch (PropertyException e) {
e.printStackTrace();
}
}
}
public String getStrippedComponentName(String propertyName) {
assert propertyName != null;
while (propertyName.startsWith("$"))
propertyName = globalProperties.get(ConfigurationManagerUtils.stripGlobalSymbol(propertyName)).toString();
return propertyName;
}
/** Adds a new listener for configuration change events.
* @param l listener to add
**/
public void addConfigurationChangeListener(ConfigurationChangeListener l) {
if (l == null)
return;
changeListeners.add(l);
}
/** Removes a listener for configuration change events.
* @param l listener to remove
**/
public void removeConfigurationChangeListener(ConfigurationChangeListener l) {
if (l == null)
return;
changeListeners.remove(l);
}
/**
* Informs all registered <code>ConfigurationChangeListener</code>s about a configuration changes the component
* named <code>configurableName</code>.
*/
void fireConfChanged(String configurableName, String propertyName) {
assert getComponentNames().contains(configurableName);
for (ConfigurationChangeListener changeListener : changeListeners)
changeListener.configurationChanged(configurableName, propertyName, this);
}
/**
* Informs all registered <code>ConfigurationChangeListener</code>s about the component previously namesd
* <code>oldName</code>
*/
void fireRenamedConfigurable(String oldName, String newName) {
assert getComponentNames().contains(newName);
for (ConfigurationChangeListener changeListener : changeListeners) {
changeListener.componentRenamed(this, getPropertySheet(newName), oldName);
}
}
/**
* Test whether the given configuration manager instance equals this instance in terms of same configuration. This
* This equals implementation does not care about instantiation of components.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ConfigurationManager))
return false;
ConfigurationManager cm = (ConfigurationManager) obj;
Set<String> thisCompNames = getComponentNames();
if (!thisCompNames.equals(cm.getComponentNames()))
return false;
// make sure that all components are the same
for (String instanceName : thisCompNames) {
PropertySheet myPropSheet = getPropertySheet(instanceName);
PropertySheet otherPropSheet = cm.getPropertySheet(instanceName);
if (!otherPropSheet.equals(myPropSheet))
return false;
}
// make sure that both configuration managers have the same set of global properties
return cm.getGlobalProperties().equals(getGlobalProperties());
}
@Override
public int hashCode() {
assert false : "hashCode not designed";
return 1; // any arbitrary constant will do
}
/** Creates a deep copy of the given CM instance. */
// This is not tested yet !!!
@Override
public ConfigurationManager clone() throws CloneNotSupportedException {
ConfigurationManager cloneCM = (ConfigurationManager)super.clone();
cloneCM.changeListeners = new ArrayList<ConfigurationChangeListener>();
cloneCM.symbolTable = new LinkedHashMap<String, PropertySheet>();
for (Map.Entry<String, PropertySheet> entry : symbolTable.entrySet()) {
cloneCM.symbolTable.put(entry.getKey(), entry.getValue().clone());
}
cloneCM.globalProperties = new HashMap<String, String>(globalProperties);
cloneCM.rawPropertyMap = new HashMap<String, RawPropertyData>(rawPropertyMap);
return cloneCM;
}
/**
* Creates an instance of the given <code>Configurable</code> by using the default parameters as defined by the
* class annotations to parameterize the component.
* @param <C> component class
* @param targetClass target class
* @return an instance of class
* @throws PropertyException if no such class is defined
*/
public static <C extends Configurable> C getInstance(Class<C> targetClass) throws PropertyException {
return getInstance(targetClass, new HashMap<String, Object>());
}
/**
* Creates an instance of the given <code>Configurable</code> by using the default parameters as defined by the
* class annotations to parameterize the component. Default parameters will be overridden if a their names are
* contained in the given <code>props</code>-map
* @param <C> component class
* @param targetClass target class
* @param props additional properties
* @return an instance of class
* @throws PropertyException if no such class is defined
*/
public static <C extends Configurable> C getInstance(Class<C> targetClass, Map<String, Object> props) throws PropertyException {
return getInstance(targetClass, props, null);
}
/**
* Creates an instance of the given <code>Configurable</code> by using the default parameters as defined by the
* class annotations to parameterize the component. Default parameters will be overridden if a their names are
* contained in the given <code>props</code>-map. The component is used to create a parameterized logger for the
* Configurable being created.
* @param <C> component class
* @param targetClass target class
* @param props additional properties
* @param compName component name
* @return an instance of class
* @throws PropertyException if no such class is defined
*/
public static <C extends Configurable> C getInstance(Class<C> targetClass, Map<String, Object> props, String compName) throws PropertyException {
PropertySheet ps = getPropSheetInstanceFromClass(targetClass, props, compName, new ConfigurationManager());
Configurable configurable = ps.getOwner();
return targetClass.cast(configurable);
}
/**
* Instantiates the given <code>targetClass</code> and instruments it using default properties or the properties
* given by the <code>defaultProps</code>.
*/
private static PropertySheet getPropSheetInstanceFromClass(Class<? extends Configurable> targetClass, Map<String, Object> defaultProps, String componentName, ConfigurationManager cm) {
RawPropertyData rpd = new RawPropertyData(componentName, targetClass.getName());
for (Map.Entry<String, Object> entry : defaultProps.entrySet()) {
Object property = entry.getValue();
if (property instanceof Class<?>)
property = ((Class<?>) property).getName();
rpd.getProperties().put(entry.getKey(), property);
}
return new PropertySheet(targetClass, componentName, cm, rpd);
}
/**
* Returns the root-logger of this configuration manager. This method is just a convenience mapper around a few CMU
* calls.
*
* @return the root logger of this CM-instance
*/
public Logger getRootLogger() {
return Logger.getLogger(ConfigurationManagerUtils.getLogPrefix(this));
}
}