package vroom.common.utilities.params;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import vroom.common.utilities.Utilities;
import vroom.common.utilities.logging.Logging;
/**
* <code>GlobalParameters</code> contains the parameters for the MSA procedure.
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a> - <a
* href="http://copa.uniandes.edu.co">Copa</a>, <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0 #updated 16-Feb-2010 10:06:49 a.m.
*/
public abstract class GlobalParameters implements Cloneable {
// -----------------------------------------------------------
// Class fields
/** A list of the parameters that are required */
private final static Map<Class<?>, Set<ParameterKey<?>>> mRequiredParameters;
/** A default persistence delegate (lazy instantiated) */
private static ParametersFilePersistenceDelegate sPersistenceDelegate;
/**
* Set the persistence delegate used to load and save parameters from/to a file
*
* @param delegate
*/
public static void setPersistenceDelegate(ParametersFilePersistenceDelegate delegate) {
sPersistenceDelegate = delegate;
}
/**
* Getter for the persistence delegate
*
* @return the persistence delegate used to load and save parameters from/to a file
*/
private static ParametersFilePersistenceDelegate getPersistenceDelegate() {
if (sPersistenceDelegate == null) {
sPersistenceDelegate = new ParametersFilePersistenceDelegate();
}
return sPersistenceDelegate;
}
/**
* Initialization of the static fields
*/
static {
mRequiredParameters = new HashMap<Class<?>, Set<ParameterKey<?>>>();
}
/**
* List of the required parameters
*
* @return a copy of the required parameters
*/
public static Collection<ParameterKey<?>> getRequiredParameters(Class<?> clazz) {
if (!mRequiredParameters.containsKey(clazz)) {
mRequiredParameters.put(clazz, new HashSet<ParameterKey<?>>());
}
return new LinkedList<ParameterKey<?>>(mRequiredParameters.get(clazz));
}
/**
* Adds a new parameter to the list of required parameters
*
* @param parameterKey
*/
public static void addRequiredParameter(Class<?> clazz, ParameterKey<?> parameterKey) {
if (!mRequiredParameters.containsKey(clazz)) {
mRequiredParameters.put(clazz, new HashSet<ParameterKey<?>>());
}
mRequiredParameters.get(clazz).add(parameterKey);
}
// -----------------------------------------------------------
/** A mapping between keys and values */
private final Map<ParameterKey<?>, Object> mParameters;
/** A set of the parameters that will possibly be defined in this instance */
private final Map<String, ParameterKey<?>> mRegisteredKeys;
/**
* Creates a new <code>GlobalParameters</code>
*/
public GlobalParameters() {
mParameters = new HashMap<ParameterKey<?>, Object>();
mRegisteredKeys = new HashMap<String, ParameterKey<?>>();
for (ParameterKey<?> k : getRequiredParameters(this.getClass()))
registerKey(k);
registerKeysByReflection(this.getClass());
resetDefaultValues();
}
/**
* Parameter key reflective registering
* <p/>
* Lookup by reflection for static fields of type {@link ParameterKey} and register them in this instance.
*
* @param clazz
* the class to be analyzed
*/
public void registerKeysByReflection(Class<?> clazz) {
for (Field f : clazz.getFields()) {
if (Modifier.isStatic(f.getModifiers())
&& ParameterKey.class.isAssignableFrom(f.getType())) {
try {
ParameterKey<?> key = (ParameterKey<?>) f.get(null);
if (key != null) {
registerKey(key);
}
} catch (IllegalArgumentException e) {
// Do nothing: ignore the field
} catch (IllegalAccessException e) {
// Do nothing: ignore the field
}
}
}
}
/**
* Required parameter key reflective adding
* <p/>
* Lookup by reflection for static fields of type {@link ParameterKey} with the {@link RequiredParameter} annotation
* and add them to the list of required parameters
*
* @param clazz
* the class to be analyzed
*/
public static void addRequiredKeysByReflection(Class<?> clazz) {
for (Field f : clazz.getFields()) {
if (Modifier.isStatic(f.getModifiers())
&& f.getAnnotation(RequiredParameter.class) != null
&& ParameterKey.class.isAssignableFrom(f.getType())) {
try {
ParameterKey<?> key = (ParameterKey<?>) f.get(null);
if (key != null) {
addRequiredParameter(clazz, key);
}
} catch (IllegalArgumentException e) {
// Do nothing: ignore the field
} catch (IllegalAccessException e) {
// Do nothing: ignore the field
}
}
}
}
/**
* Return all the keys declared in the type {@code clazz}
*
* @param clazz
* @returnall the keys declared in the type {@code clazz}
*/
public static List<ParameterKey<?>> getAllKeys(Class<?> clazz) {
Field[] fields = clazz.getFields();
ArrayList<ParameterKey<?>> keys = new ArrayList<ParameterKey<?>>(fields.length);
for (Field f : fields) {
if (Modifier.isStatic(f.getModifiers())
&& ParameterKey.class.isAssignableFrom(f.getType())) {
try {
ParameterKey<?> key = (ParameterKey<?>) f.get(null);
if (key != null) {
keys.add(key);
}
} catch (IllegalArgumentException e) {
// Do nothing: ignore the field
} catch (IllegalAccessException e) {
// Do nothing: ignore the field
}
}
}
Collections.sort(keys);
return keys;
}
/**
* Print all the keys declared in the type {@code clazz}
*
* @param clazz
* @param halFormat
* {@code true} if keys should be printed in a HAL format
*/
public static void printAllKeys(Class<?> clazz, boolean halFormat) {
List<ParameterKey<?>> keys = getAllKeys(clazz);
for (ParameterKey<?> k : keys) {
if (halFormat)
System.out.printf("%s=$%s$ ", k.getName(), k.getName().toLowerCase());
else
System.out.println(k);
}
}
/**
* Register the specified parameter in this instance.
* <p>
* This is in particular useful when loading parameters from a property file
*
* @param key
* the parameter key to be registered
* @return <code>true</code> if <code>key</code> was not already registered
*/
public boolean registerKey(ParameterKey<?> key) {
return mRegisteredKeys.put(key.getName(), key) != null;
}
/**
* Set of registered parameters
*
* @return a copy of the set containing the registered keys
*/
public Set<ParameterKey<?>> getRegisteredKeys() {
return new HashSet<ParameterKey<?>>(mRegisteredKeys.values());
}
/**
* Returns the key named {@code keyName} or <code>null</code> if it is not registered
*
* @param keyName
* @return the key named {@code keyName}
*/
public ParameterKey<?> getRegisteredKey(String keyName) {
return mRegisteredKeys.get(keyName);
}
/**
* Check if a value is associated with the given key
*
* @param key
* the key to be tested
* @return <code>true</code> if a value is associated with the given key, <code>false</code> otherwise
*/
public boolean isSet(ParameterKey<?> key) {
return mParameters.containsKey(key) && mParameters.get(key) != null;
}
/**
* Set the all the unset values to their default.
* <p>
* This methods look up for static {@link ParameterKey} fields of the current type and set the corresponding values.
* </p>
*/
public void setDefaultValues() {
for (Field f : this.getClass().getFields()) {
if (Modifier.isStatic(f.getModifiers())
&& ParameterKey.class.isAssignableFrom(f.getType())) {
try {
ParameterKey<?> key = (ParameterKey<?>) f.get(null);
if (key != null && !isSet(key)) {
setNoCheck(key, key.getDefaultValue());
}
} catch (IllegalArgumentException e) {
// Do nothing: ignore the field
} catch (IllegalAccessException e) {
// Do nothing: ignore the field
}
}
}
}
/**
* Set a parameter value.
*
* @param key
* the key for the parameter to set
* @param value
* the value to be associated with <code>key</code>
* @return the value previously associated with <code>key</code>
* @throws IllegalArgumentException
* if the <code>key</code> is <code>null</code>
* @see ParameterKey
* @see #get(ParameterKey)
*/
@SuppressWarnings("unchecked")
public <T> T set(ParameterKey<? super T> key, T value) {
return (T) setNoCheck(key, value);
}
/**
* Set a parameter value from the name of the key
*
* @param key
* the key for the parameter to set
* @param value
* the value to be associated with <code>key</code>
* @return the value previously associated with <code>key</code>
* @throws IllegalArgumentException
* if the <code>key</code> is <code>null</code>
* @see #set(ParameterKey, Object)
*/
public Object set(String key, Object value) {
if (mRegisteredKeys.containsKey(key))
return setNoCheck(mRegisteredKeys.get(key), value);
else
return setNoCheck(new ParameterKey<Object>(key, Object.class), value);
}
/**
* Set a parameter value without build-in type checking.
* <p>
* <b>Use {@link #set(ParameterKey, Object)} when possible</b>
*
* @param key
* the key for the parameter to set
* @param value
* the value to be associated with <code>key</code>
* @return the value previously associated with <code>key</code>
* @throws IllegalArgumentException
* if the <code>key</code> is <code>null</code>
* @see ParameterKey
* @see #get(ParameterKey)
* @see #set(ParameterKey, Object)
*/
public Object setNoCheck(ParameterKey<?> key, Object value) {
if (key == null) {
throw new IllegalArgumentException("Argument key cannot be null");
}
if (!key.isValueValid(value) && String.class.isAssignableFrom(value.getClass())
&& key.getParser() != null) {
// Try to cast value
value = key.getParser().parse((String) value);
}
if (!key.isValueValid(value)) {
throw new IllegalArgumentException(String.format(
"The value %s is not valid for parameter %s", value, key));
}
Object prev = mParameters.put(key, value);
// The key wasnt previously registered
if (registerKey(key)) {
Logging.getSetupLogger().lowDebug(
"Setting parameter asociated with the unregistered key %s", key);
}
Logging.getSetupLogger().lowDebug(
"Setting parameter - key:%s, value:%s, previous value:%s", key, value, prev);
return prev;
}
/**
* Reading of a parameter value.
*
* @param key
* the key for the desired parameter
* @return the value associated with the given <code>key</code>, <code>null</code> if there is no value associated
* with <code>key</code>
* @throws IllegalArgumentException
* if no value is associated with <code>key</code>
*/
public Object get(String keyName) throws IllegalArgumentException {
return get(new ParameterKey<>(keyName, Object.class));
}
/**
* Reading of a parameter value.
*
* @param key
* the key for the desired parameter
* @return the value associated with the given <code>key</code>, <code>null</code> if there is no value associated
* with <code>key</code>
* @throws IllegalArgumentException
* if no value is associated with <code>key</code>
*/
@SuppressWarnings("unchecked")
public <T> T get(ParameterKey<T> key) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("Argument key cannot be null");
}
if (!isSet(key)) {
Logging.getSetupLogger()
.warn("%s.getParameter: no value associated with key %s, returning default value %s",
this.getClass().getSimpleName(), key,
Utilities.toString(key.getDefaultValue()));
return key.getDefaultValue();
} else {
return (T) mParameters.get(key);
}
}
/**
* Factory method for parameters which values are types
*
* @param <O>
* the type of object to be returned
* @param key
* the key of the considered parameter
* @param argsTypes
* the types of the arguments to be passed to the class constructor
* @param args
* the arguments to be passed to the class constructor (can include <code>null</code> elements)
* @return a new instance of the class defined as value of the parameter <code>key</code>
* @throws IllegalArgumentException
* if the new object could not be instantiated because of a error in the arguments
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <O> O newInstanceSafe(ParameterKey<Class> key, Class<?>[] argsTypes, Object... args)
throws IllegalArgumentException {
Class<O> clazz = get(key);
if (clazz == null) {
throw new IllegalArgumentException(String.format(
"The value associated with key %s is null", key));
}
return Utilities.newInstance(clazz, argsTypes, args);
}
/**
* Factory method for parameters which values are types. <br/>
* Will deduce the constructor by using the <code>class</code> of the given arguments <code>args</code>
*
* @param <O>
* the type of object to be returned
* @param key
* the key of the considered parameter
* @param args
* the arguments to be passed to the class constructor (all elements should be <b>not <code>null</code>
* </b>)
* @return a new instance of the class defined as value of the parameter <code>key</code>
* @throws IllegalArgumentException
* if the new object could not be instantiated because of a error in the arguments
* @see GlobalParameters#newInstanceSafe(ParameterKey, Class[], Object[])
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <O> O newInstance(ParameterKey<Class> key, Object... args)
throws IllegalArgumentException {
Class<O> clazz = get(key);
if (clazz == null) {
throw new IllegalArgumentException(String.format(
"The value associated with key %s is null", key));
}
return Utilities.newInstance(clazz, args);
}
/**
* Reset to their default values all the required parameters
*/
public void resetDefaultValues() {
for (ParameterKey<?> key : getRequiredParameters(this.getClass())) {
resetDefaultValue(key);
}
}
/**
* Reset the given parameter to its default value
*
* @param the
* {@link ParameterKey} describing the considered parameter
*/
public <T> void resetDefaultValue(ParameterKey<T> key) {
set(key, key.getDefaultValue());
}
/**
* Remove all mapping between parameters and values
*/
public void clear() {
mParameters.clear();
}
/**
* Loading of global parameters from a file, overwriting previous values.
*
* @param file
* The file from which global parameters will be loaded
* @throws Exception
* @see ParametersFilePersistenceDelegate#loadParameters(GlobalParameters, java.io.File)
*/
public void loadParameters(java.io.File file) throws Exception {
getPersistenceDelegate().loadParameters(this, file);
}
/**
* Saving of global parameters to a file
*
* @param file
* The file in which global parameters will be saved
* @param omitDefaults
* <code>true</code> if default values should be omitted
* @throws IOException
* @see ParametersFilePersistenceDelegate#saveParameters(GlobalParameters, java.io.File, boolean)
*/
public void saveParameters(java.io.File file, boolean omitDefaults) throws IOException {
getPersistenceDelegate().saveParameters(this, file, omitDefaults);
}
/**
* Checking of the required parameters
*
* @return a collection containing the missing parameters from the one returned by
* {@link GlobalParameters#getRequiredParameters()}
*/
public final Collection<ParameterKey<?>> checkRequiredParameters() {
Collection<ParameterKey<?>> missing = new LinkedList<ParameterKey<?>>();
for (ParameterKey<?> param : getRequiredParameters(this.getClass())) {
if (!checkParameter(param)) {
missing.add(param);
}
}
return missing;
}
/**
* Checking of a single parameter
*
* @param key
* @return <code>true</code> if the parameter associated with <code>key</code> is defined and not <code>null</code>,
* <code>false</code> otherwise
*/
public boolean checkParameter(ParameterKey<?> key) {
return mParameters.containsKey(key) && mParameters.get(key) != null;
}
/**
* Mapping of parameters to values
*
* @return a copy of the {@link Map} containing the mapping between keys and values
*/
public Set<Entry<ParameterKey<?>, Object>> getParametersMapping() {
return mParameters.entrySet();
}
/**
* Mapping of parameters to values as a string
*
* @return a string containing the mapping in the form key=value
*/
public String getParametersMappingAsString() {
StringBuilder s = new StringBuilder();
for (Entry<ParameterKey<?>, Object> e : getParametersMapping()) {
if (e.getKey() instanceof ClassParameterKey<?> && e.getValue() != null) {
s.append(e.getKey().getName() + "=" + ((Class<?>) e.getValue()).getCanonicalName());
} else {
s.append(e.getKey().getName() + "=" + e.getValue());
}
s.append("\n");
}
return s.toString();
}
@Override
public String toString() {
StringBuffer b = new StringBuffer();
ArrayList<ParameterKey<?>> keys = new ArrayList<ParameterKey<?>>(mParameters.keySet());
Collections.sort(keys);
for (ParameterKey<?> key : keys) {
b.append(key.getName());
b.append("=");
b.append(Utilities.toString(get(key)));
b.append("\n");
}
return b.toString();
}
/**
* Clone this instance. Parameter values will have shared references
*/
@Override
public GlobalParameters clone() {
try {
GlobalParameters clone = getClass().newInstance();
for (Entry<ParameterKey<?>, Object> e : this.mParameters.entrySet()) {
clone.setNoCheck(e.getKey(), e.getValue());
}
return clone;
} catch (InstantiationException e) {
Logging.getBaseLogger().exception("GlobalParameters.clone", e);
return null;
} catch (IllegalAccessException e) {
Logging.getBaseLogger().exception("GlobalParameters.clone", e);
return null;
}
}
}// end GlobalParameters