package edu.cmu.sphinx.util.props;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.logging.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import edu.cmu.sphinx.util.SphinxLogFormatter;
/**
* Some static utility methods which ease the handling of system configurations.
*
* @author Holger Brandl
*/
public final class ConfigurationManagerUtils {
// this pattern matches strings of the form '${word}'
private static final Pattern globalSymbolPattern = Pattern.compile("\\$\\{(\\w+)\\}");
/**
* A common property (used by all components) that sets the log level for the component.
*/
public final static String GLOBAL_COMMON_LOGLEVEL = "logLevel";
/**
* The default file suffix of configuration files.
*/
public static final String CM_FILE_SUFFIX = ".sxl";
// disabled constructor because the class is just a collection of utilities for handling system configurations
private ConfigurationManagerUtils() {
}
/**
* Validates that only annotated property names have been used to setup this instance of {@code
* edu.cmu.sphinx.util.props.ConfigurationManager}.
*
* @param cm Configuration manager
* @return {@code true} if it is a valid configuration.
*/
public boolean validateConfiguration(ConfigurationManager cm) {
for (String compName : cm.getComponentNames()) {
if (!cm.getPropertySheet(compName).validate())
return false;
}
return true;
}
/**
* Strips the ${ and } off of a global symbol of the form ${symbol}.
*
* @param symbol the symbol to strip
* @return the stripped symbol
*/
public static String stripGlobalSymbol(String symbol) {
Matcher matcher = globalSymbolPattern.matcher(symbol);
if (matcher.matches()) {
return matcher.group(1);
} else {
return symbol;
}
}
public static void editConfig(ConfigurationManager cm, String name) {
PropertySheet ps = cm.getPropertySheet(name);
boolean done;
if (ps == null) {
System.out.println("No component: " + name);
return;
}
System.out.println(name + ':');
Collection<String> propertyNames = ps.getRegisteredProperties();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for (String propertyName : propertyNames) {
try {
Object value = ps.getRaw(propertyName);
String svalue;
if (value instanceof List<?>) {
continue;
} else if (value instanceof String) {
svalue = (String) value;
} else {
svalue = "DEFAULT";
}
done = false;
while (!done) {
System.out.print(" " + propertyName + " [" + svalue + "]: ");
String in = br.readLine();
if (in.isEmpty()) {
done = true;
} else if (in.equals(".")) {
return;
} else {
cm.getPropertySheet(name).setRaw(propertyName, in);
done = true;
}
}
} catch (IOException ioe) {
System.out.println("Trouble reading input");
return;
}
}
}
// remark: the replacement of xml/sxl suffix is not necessary and just done to improve readability
public static String getLogPrefix(ConfigurationManager cm) {
if (cm.getConfigURL() != null)
return new File(cm.getConfigURL().getFile()).getName().replace(".sxl", "").replace(".xml", "") + '.';
else
return "S4CM.";
}
/**
* Configure the logger
* @param cm Configuration manager
*/
public static void configureLogger(ConfigurationManager cm) {
// Allow others to override the logging settings.
if (System.getProperty("java.util.logging.config.class") != null
|| System.getProperty("java.util.logging.config.file") != null) {
return;
}
// apply the log level (if defined) for the root logger (because we're using package based logging now)
String cmPrefix = getLogPrefix(cm);
Logger cmRootLogger = Logger.getLogger(cmPrefix.substring(0, cmPrefix.length() - 1));
// we need to determine the root-level here, because the logManager will reset it
Level rootLevel = Logger.getLogger("").getLevel();
configureLogger(cmRootLogger);
String level = cm.getGlobalProperty(GLOBAL_COMMON_LOGLEVEL);
if (level == null)
level = Level.WARNING.getName();
cmRootLogger.setLevel(Level.parse(level));
// restore the old root logger level
Logger.getLogger("").setLevel(rootLevel);
}
/**
* Configures a logger to use the sphinx4-log-formatter.
* @param logger logger to configure
*/
public static void configureLogger(Logger logger) {
logger.setUseParentHandlers(false);
boolean hasHandler = false;
for (Handler handler : logger.getHandlers()) {
if (handler.getFormatter() instanceof SphinxLogFormatter) {
hasHandler = true;
break;
}
}
if (!hasHandler) {
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SphinxLogFormatter());
logger.addHandler(handler);
}
}
/**
* This method will automatically rename all components of <code>subCM</code> for which there is component named the
* same in the <code>baseCM</code> .
* <p>
* Note: This is required when merging two system configurations into one.
*
* @param baseCM base configuration manager
* @param subCM other configuration manager
* @return A map which maps all renamed component names to their new names.
*/
public static Map<String, String> fixDuplicateNames(ConfigurationManager baseCM, ConfigurationManager subCM) {
Map<String, String> renames = new HashMap<String, String>();
for (String compName : subCM.getComponentNames()) {
String uniqueName = compName;
int i = 0;
while (baseCM.getComponentNames().contains(uniqueName) ||
(subCM.getComponentNames().contains(uniqueName) && !uniqueName.equals(compName))) {
i++;
uniqueName = compName + i;
}
subCM.renameConfigurable(compName, uniqueName);
renames.put(compName, uniqueName);
}
return renames;
}
/**
* Converts a configuration manager instance into a xml-string .
* Note: This methods will not instantiate configurables.
*
* @param cm configuration manager
* @return xml representation
*/
public static String toXML(ConfigurationManager cm) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
sb.append("\n<!-- Sphinx-4 Configuration file--> \n\n");
sb.append("<config>");
Pattern pattern = Pattern.compile("\\$\\{(\\w+)\\}");
Map<String, String> globalProps = cm.getGlobalProperties();
for (Map.Entry<String, String> entry : globalProps.entrySet()) {
String propName = entry.getKey();
Matcher matcher = pattern.matcher(propName);
propName = matcher.matches() ? matcher.group(1) : propName;
sb.append("\n\t<property name=\"").append(propName).append("\" value=\"").append(entry.getValue()).append("\"/>");
}
for (String instanceName : cm.getComponentNames())
sb.append("\n\n").append(propSheet2XML(instanceName, cm.getPropertySheet(instanceName)));
sb.append("\n</config>");
return sb.toString();
}
private static String propSheet2XML(String instanceName, PropertySheet ps) {
StringBuilder sb = new StringBuilder();
sb.append("\t<component name=\"").append(instanceName).append("\" type=\"").append(ps.getConfigurableClass().getName()).append("\">");
for (String propName : ps.getRegisteredProperties()) {
String predec = "\n\t\t<property name=\"" + propName + "\" ";
if (ps.getRawNoReplacement(propName) == null)
continue; // if the property was net defined within the xml-file
switch (ps.getType(propName)) {
case COMPONENT_LIST:
sb.append("\n\t\t<propertylist name=\"").append(propName).append("\">");
List<String> compNames = toStringList(ps.getRawNoReplacement(propName));
for (String compName : compNames)
sb.append("\n\t\t\t<item>").append(compName).append("</item>");
sb.append("\n\t\t</propertylist>");
break;
default:
sb.append(predec).append("value=\"").append(ps.getRawNoReplacement(propName)).append("\"/>");
}
}
sb.append("\n\t</component>\n\n");
return sb.toString();
}
public static void save(ConfigurationManager cm, File cmLocation) {
if (!cmLocation.getName().endsWith(CM_FILE_SUFFIX))
System.err.println("WARNING: Serialized s4-configuration should have the suffix '" + CM_FILE_SUFFIX + '\'');
assert cm != null;
try {
PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(cmLocation), Charset.forName("UTF-8")));
String configXML = ConfigurationManagerUtils.toXML(cm);
pw.print(configXML);
pw.flush();
pw.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
/**
* Shows the current configuration
* @param cm configuration manager
*/
public static void showConfig(ConfigurationManager cm) {
System.out.println(" ============ config ============= ");
for (String allName : cm.getInstanceNames(Configurable.class)) {
showConfig(cm, allName);
}
}
/**
* Show the configuration for the component with the given name
*
* @param cm configuration manager
* @param name the component name
*/
public static void showConfig(ConfigurationManager cm, String name) {
// Symbol symbol = cm.getsymbolTable.get(name);
if (!cm.getComponentNames().contains(name)) {
System.out.println("No component: " + name);
return;
}
System.out.println(name + ':');
PropertySheet properties = cm.getPropertySheet(name);
for (String propertyName : properties.getRegisteredProperties()) {
System.out.print(" " + propertyName + " = ");
Object obj;
obj = properties.getRaw(propertyName);
if (obj instanceof String) {
System.out.println(obj);
} else if (obj instanceof List<?>) {
List<?> l = (List<?>) obj;
for (Iterator<?> k = l.iterator(); k.hasNext();) {
System.out.print(k.next());
if (k.hasNext()) {
System.out.print(", ");
}
}
System.out.println();
} else {
System.out.println("[DEFAULT]");
}
}
}
/**
* Applies the system properties to the raw property map. System properties should be of the form
* compName[paramName]=paramValue
* <p>
* List types cannot currently be set from system properties.
*
* @param rawMap the map of raw property values
* @param global global properties
* @throws PropertyException if an attempt is made to set a parameter for an unknown component.
*/
static void applySystemProperties(Map<String, RawPropertyData> rawMap, Map<String, String> global)
throws PropertyException {
Properties props = System.getProperties();
for (Enumeration<?> e = props.keys(); e.hasMoreElements();) {
String param = (String) e.nextElement();
String value = props.getProperty(param);
// search for parameters of the form component[parameter]=value
// these go in the property sheet for the component
int lb = param.indexOf('[');
int rb = param.indexOf(']');
if (lb > 0 && rb > lb) {
String compName = param.substring(0, lb);
String paramName = param.substring(lb + 1, rb);
RawPropertyData rpd = rawMap.get(compName);
if (rpd != null) {
rpd.add(paramName, value);
} else {
throw new InternalConfigurationException(compName, param,
"System property attempting to set parameter "
+ " for unknown component " + compName
+ " (" + param + ')');
}
}
// look for parameters of the form foo=bar
// these go in the global map
else if (param.indexOf('.') == -1) {
global.put(param, value);
}
}
}
/**
* Renames a given <code>Configurable</code>. The configurable component named <code>oldName</code> is assumed to be
* registered to the CM. Renaming does not only affect the configurable itself but possibly global property values
* and properties of other components.
*/
static void renameComponent(ConfigurationManager cm, String oldName, String newName) {
assert cm != null;
assert oldName != null && newName != null;
if (cm.getPropertySheet(oldName) == null) {
throw new RuntimeException("no configurable (to be renamed) named " + oldName + " is contained in the CM");
}
// this iteration is a little hacky. It would be much better to maintain the links to a configurable in a special table
for (String instanceName : cm.getComponentNames()) {
PropertySheet propSheet = cm.getPropertySheet(instanceName);
for (String propName : propSheet.getRegisteredProperties()) {
if (propSheet.getRawNoReplacement(propName) == null)
continue; // if the property was net defined within the xml-file
switch (propSheet.getType(propName)) {
case COMPONENT_LIST:
List<String> compNames = toStringList(propSheet.getRawNoReplacement(propName));
for (int i = 0; i < compNames.size(); i++) {
String compName = compNames.get(i);
if (compName.equals(oldName)) {
compNames.set(i, newName);
}
}
break;
case COMPONENT:
if (propSheet.getRawNoReplacement(propName).equals(oldName)) {
propSheet.setRaw(propName, newName);
}
default:
break;
}
}
}
PropertySheet ps = cm.getPropertySheet(oldName);
ps.setInstanceName(newName);
// it might be possible that the component is the value of a global property
for (Map.Entry<String, String> entry : cm.getGlobalProperties().entrySet()) {
if (entry.getValue().equals(oldName))
cm.setGlobalProperty(entry.getKey(), newName);
}
}
/**
* Gets a resource associated with the given parameter name given an property sheet.
*
* @param name the parameter name
* @param ps The property sheet which contains the property
* @return the resource associated with the name or NULL if it doesn't exist.
* @throws PropertyException if the resource cannot be found
*/
public static URL getResource(String name, PropertySheet ps) throws PropertyException {
String location = ps.getString(name);
if (location == null) {
throw new InternalConfigurationException(ps.getInstanceName(), name, "Required resource property '" + name + "' not set");
}
try {
URL url = resourceToURL(location);
if (url == null) {
throw new InternalConfigurationException(ps.getInstanceName(), name, "Can't locate " + location);
}
return url;
} catch (MalformedURLException e) {
throw new InternalConfigurationException(e, ps.getInstanceName(), name, "Bad URL " + location + e.getMessage());
}
}
final static Pattern jarPattern = Pattern.compile("resource:(.*)", Pattern.CASE_INSENSITIVE);
public static URL resourceToURL(String location) throws MalformedURLException {
Matcher jarMatcher = jarPattern.matcher(location);
if (jarMatcher.matches()) {
String resourceName = jarMatcher.group(1);
return ConfigurationManagerUtils.class.getResource(resourceName);
} else {
if (location.indexOf(':') == -1) {
location = "file:" + location;
}
return new URL(location);
}
}
/**
* @param derived derived class
* @param parent parent class
* @return <code>true</code> if <code>aClass</code> is either equal to <code>poosibleParent</code>, a subclass of
* it, or implements it if <code>possibleParent</code> is an interface.
*/
public static boolean isDerivedClass(Class<?> derived, Class<?> parent) {
return parent.isAssignableFrom(derived);
}
public static boolean isImplementingInterface(Class<?> aClass, Class<?> interfaceClass) {
assert interfaceClass.isInterface();
Class<?> superClass = aClass.getSuperclass();
if (superClass != null && isImplementingInterface(superClass, interfaceClass))
return true;
for (Class<?> curInterface : aClass.getInterfaces()) {
if (curInterface.equals(interfaceClass) || isImplementingInterface(curInterface, interfaceClass))
return true;
}
return false;
}
public static boolean isSubClass(Class<?> aClass, Class<?> possibleSuperclass) {
while (aClass != null && !aClass.equals(Object.class)) {
aClass = aClass.getSuperclass();
if (aClass != null && aClass.equals(possibleSuperclass))
return true;
}
return false;
}
/**
* Why do we need this method? The reason is, that we would like to avoid this method to be part of the
* <code>PropertySheet</code>-API. In some circumstances it is nevertheless required to get access to the managing
* <code>ConfigurationManager</code>.
* @param ps Property sheet
* @return the new configuration manager
*/
public static ConfigurationManager getPropertyManager(PropertySheet ps) {
return ps.getPropertyManager();
}
/**
* Returns a map of all component-properties of this config-manager (including their associated property-sheets.
*
* @param cm configuration manager
* @return map with properties
*/
public static Map<String, List<PropertySheet>> listAllsPropNames(ConfigurationManager cm) {
Map<String, List<PropertySheet>> allProps = new HashMap<String, List<PropertySheet>>();
for (String configName : cm.getComponentNames()) {
PropertySheet ps = cm.getPropertySheet(configName);
for (String propName : ps.getRegisteredProperties()) {
if (!allProps.containsKey(propName))
allProps.put(propName, new ArrayList<PropertySheet>());
allProps.get(propName).add(ps);
}
}
return allProps;
}
public static void dumpPropStructure(ConfigurationManager cm) {
Map<String, List<PropertySheet>> allProps = listAllsPropNames(cm);
System.out.println("Property-structure of '" + cm.getConfigURL() + "':");
// print non-ambiguous props first
System.out.println("\nUnambiguous properties = ");
for (Map.Entry<String, List<PropertySheet>> entry : allProps.entrySet()) {
if (entry.getValue().size() == 1)
System.out.print(entry.getKey() + ", ");
}
// now print ambiguous properties (including the associated components
System.out.println("\n\nAmbiguous properties: ");
for (Map.Entry<String, List<PropertySheet>> entry : allProps.entrySet()) {
if (entry.getValue().size() == 1)
continue;
System.out.print(entry.getKey() + '=');
for (PropertySheet ps : entry.getValue()) {
System.out.print(ps.getInstanceName() + ", ");
}
System.out.println();
}
}
/**
* Attempts to set the value of an arbitrary component-property. If the property-name is ambiguous with respect to
* the given <code>ConfiguratioManager</code> an extended syntax (componentName->propName) can be used to access the
* property.
* <p>
* Beside component properties it is also possible to modify the class of a configurable, but this is only allowed if
* the configurable under question has not been instantiated yet. Furthermore the user has to ensure to set all
* mandatory component properties.
* @param cm configuration manager
* @param propName property to set
* @param propValue value to set
*/
public static void setProperty(ConfigurationManager cm, String propName, String propValue) {
assert propValue != null;
Map<String, List<PropertySheet>> allProps = listAllsPropNames(cm);
Set<String> configurableNames = cm.getComponentNames();
if (!allProps.containsKey(propName) && !propName.contains("->") && !configurableNames.contains(propName))
throw new RuntimeException("No property or configurable '" + propName + "' in configuration '" + cm.getConfigURL() + "'!");
// if a configurable-class should be modified
if (configurableNames.contains(propName)) {
try {
final Class<? extends Configurable> confClass = Class.forName(propValue).asSubclass(Configurable.class);
ConfigurationManagerUtils.setClass(cm.getPropertySheet(propName), confClass);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return;
}
if (!propName.contains("->") && allProps.get(propName).size() > 1) {
throw new RuntimeException("Property-name '" + propName + "' is ambiguous with respect to configuration '"
+ cm.getConfigURL() + "'. Use 'componentName->propName' to disambiguate your request.");
}
String componentName;
// if disambiguation syntax is used find the correct PS first
if (propName.contains("->")) {
String[] splitProp = propName.split("->");
componentName = splitProp[0];
propName = splitProp[1];
} else {
componentName = allProps.get(propName).get(0).getInstanceName();
}
setProperty(cm, componentName, propName, propValue);
}
public static void setProperty(ConfigurationManager cm, String componentName, String propName, String propValue) {
// now set the property
PropertySheet ps = cm.getPropertySheet(componentName);
if (ps == null)
throw new RuntimeException("Component '" + propName + "' is not registered to this system configuration '");
// set the value to null if the string content is 'null
if (propValue.equals("null"))
propValue = null;
switch (ps.getType(propName)) {
case BOOLEAN:
ps.setBoolean(propName, Boolean.parseBoolean(propValue));
break;
case DOUBLE:
ps.setDouble(propName, Double.parseDouble(propValue));
break;
case INT:
ps.setInt(propName, Integer.parseInt(propValue));
break;
case STRING:
ps.setString(propName, propValue);
break;
case COMPONENT:
ps.setComponent(propName, propValue, null);
break;
case COMPONENT_LIST:
List<String> compNames = new ArrayList<String>();
for (String component : propValue.split(";")) {
compNames.add(component.trim());
}
ps.setComponentList(propName, compNames, null);
break;
default:
throw new RuntimeException("unknown property-type");
}
}
public static URL getURL(File file) {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
/**
* Returns the not yet instantiated components registered to this configuration manager.
*
* @param cm configuration manager
* @return collection of instantiated components
*/
public static Collection<String> getNonInstaniatedComps(ConfigurationManager cm) {
Collection<String> nonInstComponents = new ArrayList<String>();
for (String compName : cm.getComponentNames()) {
if (!cm.getPropertySheet(compName).isInstanciated())
nonInstComponents.add(compName);
}
return nonInstComponents;
}
public static void setClass(PropertySheet ps, Class<? extends Configurable> confClass) {
if (ps.isInstanciated())
throw new RuntimeException("configurable " + ps.getInstanceName() + "has already been instantiated");
ps.setConfigurableClass(confClass);
}
public static List<String> toStringList(Object obj) {
List<String> result = new ArrayList<String>();
if (!(obj instanceof List<?>))
return null;
for (Object o : (List<?>) obj) {
if (o instanceof String) {
result.add((String) o);
}
}
return result;
}
}