package org.opentrafficsim.base.parameters;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.djunits.unit.DimensionlessUnit;
import org.djunits.value.vdouble.scalar.Dimensionless;
import org.djutils.exceptions.Throw;
import org.djutils.reflection.ClassUtil;
/**
* Implementation of {@link Parameters} with methods to initialize the set of parameters.
*
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 13, 2016
* @author Alexander Verbraeck
* @author Wouter Schakel
*/
public class ParameterSet implements Parameters, Serializable
{
/** */
private static final long serialVersionUID = 20160400L;
/** Object to recognize that no value was set previously. */
private static final Empty EMPTY = new Empty();
/** Whether to copy internal data on write. */
private boolean copyOnWrite = false;
/** List of parameters. */
private Map, Object> parameters;
/** List of parameters with values before last set. */
private Map, Object> previous;
/**
* Construct a new, empty Parameters set.
*/
public ParameterSet()
{
this.parameters = new LinkedHashMap<>();
this.previous = new LinkedHashMap<>();
}
/**
* Constructor which creates a copy of the input set.
* @param parameters Parameters; input set to copy into the new Parameters object
*/
public ParameterSet(final Parameters parameters)
{
if (parameters instanceof ParameterSet)
{
ParameterSet parameterSet = (ParameterSet) parameters;
this.parameters = parameterSet.parameters;
this.previous = parameterSet.previous;
this.copyOnWrite = true;
parameterSet.copyOnWrite = true;
}
else
{
parameters.setAllIn(this);
}
}
/** {@inheritDoc} */
@Override
public final void setParameter(final ParameterType parameterType, final T value) throws ParameterException
{
Throw.when(value == null, ParameterException.class,
"Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
saveSetParameter(parameterType, value, false);
}
/** {@inheritDoc} */
@Override
public final void setParameterResettable(final ParameterType parameterType, final T value) throws ParameterException
{
Throw.when(value == null, ParameterException.class,
"Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
saveSetParameter(parameterType, value, true);
}
/**
* Sets a parameter value while checking conditions.
* @param parameterType ParameterType<T>; the parameter type
* @param value T; new value for the parameter
* @param resettable boolean; whether the parameter set should be resettable
* @param Class of the value
* @throws ParameterException If the value does not comply with constraints.
*/
private void saveSetParameter(final ParameterType parameterType, final T value, final boolean resettable)
throws ParameterException
{
parameterType.check(value, this);
parameterType.checkConstraint(value);
checkCopyOnWrite();
if (resettable)
{
Object prevValue = this.parameters.get(parameterType);
if (prevValue == null)
{
// remember that there was no value before this set
this.previous.put(parameterType, EMPTY);
}
else
{
this.previous.put(parameterType, prevValue);
}
}
else
{
// no reset after non-resettale set
this.previous.remove(parameterType);
}
this.parameters.put(parameterType, value);
}
/** {@inheritDoc} */
@Override
public final void resetParameter(final ParameterType> parameterType) throws ParameterException
{
checkCopyOnWrite();
Object prevValue = this.previous.remove(parameterType);
Throw.when(prevValue == null, ParameterException.class,
"Reset on parameter of type '%s' could not be performed, it was not set resettable.", parameterType.getId());
if (prevValue instanceof Empty)
{
// no value was set before last set, so make parameter type not set
this.parameters.remove(parameterType);
}
else
{
this.parameters.put(parameterType, prevValue);
}
}
/**
* Copy the internal data if needed.
*/
private void checkCopyOnWrite()
{
if (this.copyOnWrite)
{
this.parameters = new LinkedHashMap<>(this.parameters);
this.previous = new LinkedHashMap<>(this.previous);
this.copyOnWrite = false;
}
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public T getParameter(final ParameterType parameterType) throws ParameterException
{
@SuppressWarnings("unchecked")
// set methods guarantee matching of parameter type and value
T result = (T) this.parameters.get(parameterType);
Throw.when(result == null, ParameterException.class, "Could not get parameter of type '%s' as it was not set.",
parameterType.getId());
return result;
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public final T getParameterOrNull(final ParameterType parameterType)
{
// set methods guarantee matching of parameter type and value
return (T) this.parameters.get(parameterType);
}
/** {@inheritDoc} */
@Override
public final boolean contains(final ParameterType> parameterType)
{
return this.parameters.containsKey(parameterType);
}
/**
* Returns a safe copy of the parameters.
* @return Map<AbstractParameterType<?>>; a safe copy of the parameters, e.g., for printing
*/
public final Map, Object> getParameters()
{
return new LinkedHashMap<>(this.parameters);
}
/**
* Sets the default value of a parameter. Default value sets are not resettable.
* @param parameter ParameterType<T>; the parameter to set the default value of
* @param Class of the value
* @return Parameters; this set of parameters (for method chaining)
* @throws ParameterException if the parameter type has no default value
*/
public final ParameterSet setDefaultParameter(final ParameterType parameter) throws ParameterException
{
T defaultValue = parameter.getDefaultValue();
try
{
saveSetParameter(parameter, defaultValue, false);
}
catch (ParameterException pe)
{
// should not happen, default value and parameter type are connected
throw new RuntimeException(pe);
}
return this;
}
/**
* Sets the default values of all accessible parameters defined in the given class. Default value sets are not
* resettable.
* @param clazz Class<?>; class with parameters
* @return Parameters; this set of parameters (for method chaining)
*/
public final ParameterSet setDefaultParameters(final Class> clazz)
{
return setDefaultParametersLocal(clazz);
}
/**
* Sets the default values of all accessible parameters defined in the given class. Default value sets are not resettable.
* @param clazz Class<?>; class with parameters
* @param Class of the value
* @return this set of parameters (for method chaining)
*/
@SuppressWarnings("unchecked")
private ParameterSet setDefaultParametersLocal(final Class> clazz)
{
// set all default values using reflection
Set fields = ClassUtil.getAllFields(clazz);
for (Field field : fields)
{
if (ParameterType.class.isAssignableFrom(field.getType()))
{
try
{
field.setAccessible(true);
ParameterType p = (ParameterType) field.get(clazz);
saveSetParameter(p, p.getDefaultValue(), false);
}
catch (IllegalArgumentException iare)
{
// should not happen, field and clazz are related
throw new RuntimeException(iare);
}
catch (IllegalAccessException iace)
{
// parameter type not public
throw new RuntimeException(iace);
}
catch (ParameterException pe)
{
// do not set parameter without default value
throw new RuntimeException(pe);
}
}
}
return this;
}
/** {@inheritDoc} */
@Override
public final void setAllIn(final Parameters params)
{
if (params instanceof ParameterSet)
{
ParameterSet parameterSet = (ParameterSet) params;
parameterSet.checkCopyOnWrite();
parameterSet.parameters.putAll(this.parameters);
}
else
{
setAllOneByOne(params);
}
}
/**
* Sets the parameters of this set in the given set.
* @param params Parameters; parameters to set the values in
* @param parameter value type
*/
@SuppressWarnings("unchecked")
private void setAllOneByOne(final Parameters params)
{
for (ParameterType> parameterType : this.parameters.keySet())
{
try
{
params.setParameter((ParameterType) parameterType, (T) this.parameters.get(parameterType));
}
catch (ParameterException exception)
{
throw new RuntimeException(exception); // should not happen
}
}
}
/** {@inheritDoc} */
@Override
public final String toString()
{
StringBuilder out = new StringBuilder("Parameters [");
String sep = "";
for (ParameterType> apt : this.parameters.keySet())
{
try
{
out.append(sep).append(apt.getId()).append("=").append(apt.printValue(this));
sep = ", ";
}
catch (ParameterException pe)
{
// We know the parameter has been set as we get the keySet from parameters
throw new RuntimeException(pe);
}
}
out.append("]");
return out.toString();
}
/**
* Class of object to put in the internal Map of Parameters to indicate that no value was set.
*/
private static class Empty extends Dimensionless
{
/** */
private static final long serialVersionUID = 20160414L;
/**
* Constructor for Empty.
*/
Empty()
{
super(Double.NaN, DimensionlessUnit.SI);
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "Empty []";
}
}
}