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 []"; } } }