package edu.cmu.sphinx.util.props; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * A property sheet which defines a collection of properties for a single component * in the system. * * @author Holger Brandl */ public class PropertySheet implements Cloneable { public static final String COMP_LOG_LEVEL = "logLevel"; private Map<String, S4PropWrapper> registeredProperties = new HashMap<String, S4PropWrapper>(); private Map<String, Object> propValues = new HashMap<String, Object>(); /** * Maps the names of the component properties to their (possibly unresolved) values. * <p> * Example: <code>frontend</code> to <code>${myFrontEnd}</code> */ private Map<String, Object> rawProps = new HashMap<String, Object>(); private ConfigurationManager cm; private Configurable owner; private Class<? extends Configurable> ownerClass; private String instanceName; public PropertySheet(Configurable configurable, String name, RawPropertyData rpd, ConfigurationManager ConfigurationManager) { this(configurable.getClass(), name, ConfigurationManager, rpd); owner = configurable; } public PropertySheet(Class<? extends Configurable> confClass, String name, ConfigurationManager cm, RawPropertyData rpd) { ownerClass = confClass; this.cm = cm; this.instanceName = name; parseClass(confClass); setConfigurableClass(confClass); // now apply all xml properties Map<String, Object> flatProps = rpd.flatten(cm).getProperties(); rawProps = new HashMap<String, Object>(rpd.getProperties()); for (String propName : rawProps.keySet()) propValues.put(propName, flatProps.get(propName)); } /** * Registers a new property which type and default value are defined by the given sphinx property. * * @param propName The name of the property to be registered. * @param property The property annotation masked by a proxy. */ private void registerProperty(String propName, S4PropWrapper property) { if (property == null || propName == null) throw new InternalConfigurationException(getInstanceName(), propName, "property or its value is null"); if (!registeredProperties.containsKey(propName)) registeredProperties.put(propName, property); if (!propValues.containsKey(propName)) { propValues.put(propName, null); rawProps.put(propName, null); } } /** Returns the property names <code>name</code> which is still wrapped into the annotation instance. * * @param name the name of the property * @param propertyClass the class of the property * @return the wrapper around property * @throws PropertyException if there is no such property **/ public S4PropWrapper getProperty(String name, Class<?> propertyClass) throws PropertyException { if (!propValues.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, "Unknown property '" + name + "' ! Make sure that you've annotated it."); S4PropWrapper s4PropWrapper = registeredProperties.get(name); if (s4PropWrapper == null) { throw new InternalConfigurationException(getInstanceName(), name, "Property is not an annotated property of " + getConfigurableClass()); } try { propertyClass.cast(s4PropWrapper.getAnnotation()); } catch (ClassCastException e) { throw new InternalConfigurationException(e, getInstanceName(), name, "Property annotation " + s4PropWrapper.getAnnotation() + " doesn't match the required type " + propertyClass.getName()); } return s4PropWrapper; } /** * Gets the value associated with this name * * @param name the name * @return the value */ public String getString(String name) throws PropertyException { S4PropWrapper s4PropWrapper = getProperty(name, S4String.class); S4String s4String = ((S4String) s4PropWrapper.getAnnotation()); if (propValues.get(name) == null) { boolean isDefDefined = !s4String.defaultValue().equals(S4String.NOT_DEFINED); if (s4String.mandatory()) { if (!isDefDefined) throw new InternalConfigurationException(getInstanceName(), name, "mandatory property is not set!"); } propValues.put(name, isDefDefined ? s4String.defaultValue() : null); } String propValue = flattenProp(name); // Check range List<String> range = Arrays.asList(s4String.range()); if (!range.isEmpty() && !range.contains(propValue)) throw new InternalConfigurationException(getInstanceName(), name, " is not in range (" + range + ')'); return propValue; } private String flattenProp(String name) { Object value = propValues.get(name); return value instanceof String ? (String)value : null; } /** * Gets the value associated with this name * * @param name the name * @return the value * @throws edu.cmu.sphinx.util.props.PropertyException * if the named property is not of this type */ public int getInt(String name) throws PropertyException { S4PropWrapper s4PropWrapper = getProperty(name, S4Integer.class); S4Integer s4Integer = (S4Integer) s4PropWrapper.getAnnotation(); if (propValues.get(name) == null) { boolean isDefDefined = !(s4Integer.defaultValue() == S4Integer.NOT_DEFINED); if (s4Integer.mandatory()) { if (!isDefDefined) throw new InternalConfigurationException(getInstanceName(), name, "mandatory property is not set!"); } else if (!isDefDefined) throw new InternalConfigurationException(getInstanceName(), name, "no default value for non-mandatory property"); propValues.put(name, s4Integer.defaultValue()); } Object propObject = propValues.get(name); Integer propValue = propObject instanceof Integer ? (Integer) propObject : Integer.decode(flattenProp(name)); int[] range = s4Integer.range(); if (range.length != 2) throw new InternalConfigurationException(getInstanceName(), name, Arrays.toString(range) + " is not of expected range type, which is {minValue, maxValue)"); if (propValue < range[0] || propValue > range[1]) throw new InternalConfigurationException(getInstanceName(), name, " is not in range (" + Arrays.toString(range) + ')'); return propValue; } /** * Gets the value associated with this name * * @param name the name * @return the value * @throws edu.cmu.sphinx.util.props.PropertyException * if the named property is not of this type */ public float getFloat(String name) throws PropertyException { return ((Double) getDouble(name)).floatValue(); } /** * Gets the value associated with this name * * @param name the name * @return the value * @throws edu.cmu.sphinx.util.props.PropertyException * if the named property is not of this type */ public double getDouble(String name) throws PropertyException { S4PropWrapper s4PropWrapper = getProperty(name, S4Double.class); S4Double s4Double = (S4Double) s4PropWrapper.getAnnotation(); if (propValues.get(name) == null) { boolean isDefDefined = !(s4Double.defaultValue() == S4Double.NOT_DEFINED); if (s4Double.mandatory()) { if (!isDefDefined) throw new InternalConfigurationException(getInstanceName(), name, "mandatory property is not set!"); } else if (!isDefDefined) throw new InternalConfigurationException(getInstanceName(), name, "no default value for non-mandatory property"); propValues.put(name, s4Double.defaultValue()); } Object propObject = propValues.get(name); Double propValue; if (propObject instanceof Double) propValue = (Double)propObject; else if (propObject instanceof Number) propValue = ((Number)propObject).doubleValue(); else propValue = Double.valueOf(flattenProp(name)); double[] range = s4Double.range(); if (range.length != 2) throw new InternalConfigurationException(getInstanceName(), name, Arrays.toString(range) + " is not of expected range type, which is {minValue, maxValue)"); if (propValue < range[0] || propValue > range[1]) throw new InternalConfigurationException(getInstanceName(), name, " is not in range (" + Arrays.toString(range) + ')'); return propValue; } /** * Gets the value associated with this name * * @param name the name * @return the value * @throws edu.cmu.sphinx.util.props.PropertyException * if the named property is not of this type */ public Boolean getBoolean(String name) throws PropertyException { S4PropWrapper s4PropWrapper = getProperty(name, S4Boolean.class); S4Boolean s4Boolean = (S4Boolean) s4PropWrapper.getAnnotation(); if (propValues.get(name) == null) propValues.put(name, s4Boolean.defaultValue()); Object propObject = propValues.get(name); Boolean propValue; if (propObject instanceof Boolean) propValue = (Boolean) propObject; else propValue = Boolean.valueOf(flattenProp(name)); return propValue; } /** * Gets a component associated with the given parameter name. First search * the component in property table, then try to get component by name from * the manager, then creates component with default properties. * * @param name * the parameter name * @return the component associated with the name * @throws edu.cmu.sphinx.util.props.PropertyException * if the component does not exist or is of the wrong type. */ public Configurable getComponent(String name) throws PropertyException { S4PropWrapper s4PropWrapper = getProperty(name, S4Component.class); Configurable configurable = null; S4Component s4Component = (S4Component) s4PropWrapper.getAnnotation(); Class<? extends Configurable> expectedType = s4Component.type(); Object propVal = propValues.get(name); if (propVal != null && propVal instanceof Configurable) { return (Configurable) propVal; } if (propVal != null && propVal instanceof String) { PropertySheet ps = cm.getPropertySheet(flattenProp(name)); if (ps != null) configurable = ps.getOwner(); else throw new InternalConfigurationException(getInstanceName(), name, "component '" + flattenProp(name) + "' is missing"); } if (configurable != null && !expectedType.isInstance(configurable)) throw new InternalConfigurationException(getInstanceName(), name, "mismatch between annotation and component type"); if (configurable != null) { propValues.put(name, configurable); return configurable; } configurable = getComponentFromAnnotation(name, s4Component); propValues.put(name, configurable); return configurable; } private Configurable getComponentFromAnnotation(String name, S4Component s4Component) { Configurable configurable; Class<? extends Configurable> defClass = s4Component.defaultClass(); if (defClass.equals(Configurable.class) && s4Component.mandatory()) { throw new InternalConfigurationException(getInstanceName(), name, "mandatory property is not set!"); } if (Modifier.isAbstract(defClass.getModifiers()) && s4Component.mandatory()) throw new InternalConfigurationException(getInstanceName(), name, defClass.getName() + " is abstract!"); // because we're forced to use the default type, make sure that it // is set if (defClass.equals(Configurable.class)) { if (s4Component.mandatory()) { throw new InternalConfigurationException(getInstanceName(), name, instanceName + ": no default class defined for " + name); } else { return null; } } configurable = ConfigurationManager.getInstance(defClass); if (configurable == null) { throw new InternalConfigurationException(getInstanceName(), name, "instantiation of referenenced configurable failed"); } return configurable; } /** Returns the class of of a registered component property without instantiating it. * @param propName the name of the property * @return class of the component corresponding to that property */ public Class<? extends Configurable> getComponentClass(String propName) { Class<? extends Configurable> defClass = null; if (propValues.get(propName) != null) try { Class<?> objClass = Class.forName((String) propValues.get(propName)); defClass = objClass.asSubclass(Configurable.class); } catch (ClassNotFoundException e) { PropertySheet ps = cm.getPropertySheet(flattenProp(propName)); defClass = ps.ownerClass; } else { S4Component comAnno = (S4Component) registeredProperties.get(propName).getAnnotation(); defClass = comAnno.defaultClass(); if (comAnno.mandatory()) defClass = null; } return defClass; } /** * Gets a list of float numbers associated with the given parameter name * * @param name the parameter name * @return a list of floats associated with the name. * @throws InternalConfigurationException if parameters are not double values. */ public List<String> getStringList(String name) throws InternalConfigurationException { getProperty(name, S4StringList.class); return ConfigurationManagerUtils.toStringList (propValues.get(name)); } /** * Gets a list of components associated with the given parameter name * * @param <T> parent component * @param name * the parameter name * @param tclass * the class of the list elements * @return the component associated with the name * @throws PropertyException * if the component does not exist or is of the wrong type. */ public <T> List<T> getComponentList(String name, Class<T> tclass) throws InternalConfigurationException { getProperty(name, S4ComponentList.class); List<?> components = (List<?>) propValues.get(name); assert registeredProperties.get(name).getAnnotation() instanceof S4ComponentList; S4ComponentList annotation = (S4ComponentList) registeredProperties .get(name).getAnnotation(); // no components names are available and no component list was yet // loaded therefore load the default list of components from the // annotation if (components == null) { List<Class<? extends Configurable>> defClasses = Arrays .asList(annotation.defaultList()); // if (annotation.mandatory() && defClasses.isEmpty()) // throw new InternalConfigurationException(getInstanceName(), name, // "mandatory property is not set!"); List<Configurable> defaultComponents = new ArrayList<Configurable>(); for (Class<? extends Configurable> defClass : defClasses) { defaultComponents.add(ConfigurationManager.getInstance(defClass)); } propValues.put(name, defaultComponents); } else if (!components.isEmpty() && !(components.get(0) instanceof Configurable)) { List<Configurable> resolvedComponents = new ArrayList<Configurable>(); for (Object componentName : components) { Configurable configurable = cm.lookup((String) componentName); if (configurable != null) { resolvedComponents.add(configurable); } else if (!annotation.beTolerant()) { throw new InternalConfigurationException(name, (String) componentName, "lookup of list-element '" + componentName + "' failed!"); } } propValues.put(name, resolvedComponents); } List<?> values = (List<?>) propValues.get(name); ArrayList<T> result = new ArrayList<T>(); for (Object obj : values) { if (tclass.isInstance(obj)) { result.add(tclass.cast(obj)); } else { throw new InternalConfigurationException(getInstanceName(), name, "Not all elements have required type " + tclass + " Found one of type " + obj.getClass()); } } return result; } /** * Parses the string with multiple URL's separated by ;. Return the list of * resources to load * * @param name * list with URL's * @return list of resources */ public List<URL> getResourceList(String name) { List<URL> resourceList = new ArrayList<URL>(); String pathListString = getString(name); if (pathListString != null) { for (String url : pathListString.split(";")) { try { URL resourceUrl = new URL(url); resourceList.add(resourceUrl); } catch (MalformedURLException mue) { throw new IllegalArgumentException(url + " is not a valid URL."); } } } return resourceList; } public String getInstanceName() { return instanceName; } public void setInstanceName(String newInstanceName) { this.instanceName = newInstanceName; } /** @return true if the owner of this property sheet is already instantiated. */ public boolean isInstanciated() { return !(owner == null); } /** * @return the owner of this property sheet. In most cases this will be the configurable instance which was * instrumented by this property sheet. */ public synchronized Configurable getOwner() { try { if (!isInstanciated()) { // ensure that all mandatory properties are set before instantiating the component Collection<String> undefProps = getUndefinedMandatoryProps(); if (!undefProps.isEmpty()) { throw new InternalConfigurationException(getInstanceName(), undefProps.toString(), "not all mandatory properties are defined"); } owner = ownerClass.newInstance(); owner.newProperties(this); } } catch (IllegalAccessException e) { throw new InternalConfigurationException(e, getInstanceName(), null, "Can't access class " + ownerClass); } catch (InstantiationException e) { throw new InternalConfigurationException(e, getInstanceName(), null, "Can't instantiate class " + ownerClass); } return owner; } /** * @return the set of all component properties which were tagged as mandatory but which are not set (or no default * value is given). */ public Collection<String> getUndefinedMandatoryProps() { Collection<String> undefProps = new ArrayList<String>(); for (String propName : getRegisteredProperties()) { Annotation anno = registeredProperties.get(propName).getAnnotation(); boolean isMandatory = false; if (anno instanceof S4Component) { isMandatory = ((S4Component) anno).mandatory() && ((S4Component) anno).defaultClass() == null; } else if (anno instanceof S4String) { isMandatory = ((S4String) anno).mandatory() && ((S4String) anno).defaultValue().equals(S4String.NOT_DEFINED); } else if (anno instanceof S4Integer) { isMandatory = ((S4Integer) anno).mandatory() && ((S4Integer) anno).defaultValue() == S4Integer.NOT_DEFINED; } else if (anno instanceof S4Double) { isMandatory = ((S4Double) anno).mandatory() && ((S4Double) anno).defaultValue() == S4Double.NOT_DEFINED; } if (isMandatory && !((rawProps.get(propName) != null) || (propValues.get(propName) != null))) undefProps.add(propName); } return undefProps; } /** @return the class of the owner configurable of this property sheet. */ public Class<? extends Configurable> getConfigurableClass() { return ownerClass; } /** * Sets the configurable class of this object. * * @throws RuntimeException if the the <code>Configurable</code> is already instantiated. */ void setConfigurableClass(Class<? extends Configurable> confClass) { ownerClass = confClass; // Don't allow changes of the class if the configurable has already been instantiated if (isInstanciated()) throw new RuntimeException("class is already instantiated"); // clean up the properties if necessary // registeredProperties.clear(); final Collection<String> classProperties = new HashSet<String>(); final Map<Field, Annotation> classProps = parseClass(ownerClass); for (Map.Entry<Field, Annotation> entry : classProps.entrySet()) { try { String propertyName = (String)entry.getKey().get(null); // make sure that there is not already another property with this name assert !classProperties.contains(propertyName) : "duplicate property-name for different properties: " + propertyName + " for the class " + confClass; registerProperty(propertyName, new S4PropWrapper(entry.getValue())); classProperties.add(propertyName); } catch (IllegalAccessException e) { e.printStackTrace(); } } } /** * Sets the given property to the given name * * @param name the simple property name * @param value to set * @throws PropertyException if error occurred */ public void setString(String name, String value) throws PropertyException { // ensure that there is such a property if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered string-property"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4String)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type string"); applyConfigurationChange(name, value, value); } /** * Sets the given property to the given name * * @param name the simple property name * @param value the value for the property */ public void setInt(String name, int value) throws PropertyException { // ensure that there is such a property if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered int-property"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4Integer)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type int"); applyConfigurationChange(name, value, value); } /** * Sets the given property to the given name * * @param name the simple property name * @param value the value for the property */ public void setDouble(String name, double value) throws PropertyException { // ensure that there is such a property if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered double-property"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4Double)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type double"); applyConfigurationChange(name, value, value); } /** * Sets the given property to the given name * * @param name the simple property name * @param value the value for the property */ public void setBoolean(String name, Boolean value) throws PropertyException { if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered boolean-property"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4Boolean)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type boolean"); applyConfigurationChange(name, value, value); } /** * Sets the given property to the given name * * @param name the simple property name * @param cmName the name of the configurable within the configuration manager (required for serialization only) * @param value the value for the property */ public void setComponent(String name, String cmName, Configurable value) throws PropertyException { if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered compontent"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4Component)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type component"); applyConfigurationChange(name, cmName, value); } /** * Sets the given property to the given name * * @param name the simple property name * @param valueNames the list of names of the configurables within the configuration manager (required for * serialization only) * @param value the value for the property */ public void setComponentList(String name, List<String> valueNames, List<Configurable> value) throws PropertyException { if (!registeredProperties.containsKey(name)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is not a registered component-list"); Annotation annotation = registeredProperties.get(name).getAnnotation(); if (!(annotation instanceof S4ComponentList)) throw new InternalConfigurationException(getInstanceName(), name, '\'' + name + "' is of type component-list"); rawProps.put(name, valueNames); propValues.put(name, value); applyConfigurationChange(name, valueNames, value); } private void applyConfigurationChange(String propName, Object cmName, Object value) throws PropertyException { rawProps.put(propName, cmName); propValues.put(propName, value != null ? value : cmName); if (getInstanceName() != null) cm.fireConfChanged(getInstanceName(), propName); if (owner != null) owner.newProperties(this); } /** * Sets the raw property to the given name * * @param key the simple property name * @param val the value for the property */ void setRaw(String key, Object val) { rawProps.put(key, val); propValues.put(key, null); } /** * Gets the raw value associated with this name * * @param name the name * @return the value as an object (it could be a String or a String[] depending upon the property type) */ public Object getRaw(String name) { return rawProps.get(name); } /** * Gets the raw value associated with this name, no global symbol replacement is performed. * * @param name the name * @return the value as an object (it could be a String or a String[] depending upon the property type) */ public Object getRawNoReplacement(String name) { return rawProps.get(name); } /** * Returns the type of the given property. * * @param propName the name of the property * @return the type of the property */ public PropertyType getType(String propName) { S4PropWrapper wrapper = registeredProperties.get(propName); if (wrapper == null) { throw new InternalConfigurationException(getInstanceName(), propName, " is not a valid property of" + getConfigurableClass()); } Annotation annotation = wrapper.getAnnotation(); if (annotation instanceof S4Component) return PropertyType.COMPONENT; else if (annotation instanceof S4ComponentList) return PropertyType.COMPONENT_LIST; else if (annotation instanceof S4Integer) return PropertyType.INT; else if (annotation instanceof S4Double) return PropertyType.DOUBLE; else if (annotation instanceof S4Boolean) return PropertyType.BOOLEAN; else if (annotation instanceof S4String) return PropertyType.STRING; else throw new RuntimeException("Unknown property type"); } /** * Gets the owning property manager * * @return the property manager */ ConfigurationManager getPropertyManager() { return cm; } /** * Returns a logger to use for this configurable component. The logger can be configured with the property: * 'logLevel' - The default logLevel value is defined (within the xml configuration file by the global property * 'defaultLogLevel' (which defaults to WARNING). * <p> * implementation note: the logger became configured within the constructor of the parenting configuration manager. * * @return the logger for this component * @throws edu.cmu.sphinx.util.props.PropertyException * if an error occurs */ public Logger getLogger() { Logger logger; String baseName = ConfigurationManagerUtils.getLogPrefix(cm) + ownerClass.getName(); if (instanceName != null) { logger = Logger.getLogger(baseName + '.' + instanceName); } else logger = Logger.getLogger(baseName); // if there's a logLevel set for component apply to the logger Object rawLogLevel = rawProps.get(COMP_LOG_LEVEL); if (rawLogLevel != null) logger.setLevel(rawLogLevel instanceof String ? Level.parse((String) rawLogLevel) : (Level) rawLogLevel); return logger; } /** @return the names of registered properties of this PropertySheet object. */ public Collection<String> getRegisteredProperties() { return Collections.unmodifiableCollection(registeredProperties.keySet()); } public void setCM(ConfigurationManager cm) { this.cm = cm; } /** * Returns true if two property sheet define the same object in terms of configuration. The owner (and the parent * configuration manager) are not expected to be the same. */ @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof PropertySheet)) return false; PropertySheet ps = (PropertySheet) obj; if (!rawProps.keySet().equals(ps.rawProps.keySet())) return false; // maybe we could test a little bit more here. suggestions? return true; } @Override public int hashCode() { assert false : "hashCode not designed"; return 1; // any arbitrary constant will do } @Override public String toString() { return getInstanceName() + "; isInstantiated=" + isInstanciated() + "; props=" + rawProps.keySet(); } @Override protected PropertySheet clone() throws CloneNotSupportedException { PropertySheet ps = (PropertySheet)super.clone(); ps.registeredProperties = new HashMap<String, S4PropWrapper>(this.registeredProperties); ps.propValues = new HashMap<String, Object>(this.propValues); ps.rawProps = new HashMap<String, Object>(this.rawProps); // make deep copy of raw-lists for (String regProp : ps.getRegisteredProperties()) { if (getType(regProp) == PropertyType.COMPONENT_LIST) { ps.rawProps.put(regProp, ConfigurationManagerUtils.toStringList(rawProps.get(regProp))); ps.propValues.put(regProp, null); } } ps.cm = cm; ps.owner = null; ps.instanceName = this.instanceName; return ps; } /** Validates a configuration, by ensuring that only valid property-names have been used to configure the component. * * @return if property is validated */ public boolean validate() { for (String propName : rawProps.keySet()) { if (propName.equals(ConfigurationManagerUtils.GLOBAL_COMMON_LOGLEVEL)) continue; if (!registeredProperties.containsKey(propName)) return false; } return true; } /** * use annotation based class parsing to detect the configurable properties of a <code>Configurable</code>-class * * @param configurable of type Class */ private static Map<Field, Annotation> parseClass(Class<? extends Configurable> configurable) { Field[] classFields = configurable.getFields(); Map<Field, Annotation> s4props = new HashMap<Field, Annotation>(); for (Field field : classFields) { Annotation[] annotations = field.getAnnotations(); for (Annotation annotation : annotations) { Annotation[] superAnnotations = annotation.annotationType().getAnnotations(); for (Annotation superAnnotation : superAnnotations) { if (superAnnotation instanceof S4Property) { int fieldModifiers = field.getModifiers(); assert Modifier.isStatic(fieldModifiers) : "property fields are assumed to be static"; assert Modifier.isPublic(fieldModifiers) : "property fields are assumed to be public"; assert Modifier.isFinal(fieldModifiers) : "property fields are assumed to be final"; assert field.getType().equals(String.class) : "properties fields are assumed to be instances of java.lang.String"; s4props.put(field, annotation); } } } } return s4props; } }