ParameterSet.java

  1. package org.opentrafficsim.base.parameters;

  2. import java.io.Serializable;
  3. import java.lang.reflect.Field;
  4. import java.util.LinkedHashMap;
  5. import java.util.Map;
  6. import java.util.Set;

  7. import org.djunits.unit.DimensionlessUnit;
  8. import org.djunits.value.vdouble.scalar.Dimensionless;
  9. import org.djutils.exceptions.Throw;
  10. import org.djutils.reflection.ClassUtil;

  11. /**
  12.  * Implementation of {@link Parameters} with methods to initialize the set of parameters.
  13.  * <p>
  14.  * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  15.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  16.  * <p>
  17.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 13, 2016 <br>
  18.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  19.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  20.  */
  21. public class ParameterSet implements Parameters, Serializable
  22. {

  23.     /** */
  24.     private static final long serialVersionUID = 20160400L;

  25.     /** Object to recognize that no value was set previously. */
  26.     private static final Empty EMPTY = new Empty();

  27.     /** Whether to copy internal data on write. */
  28.     private boolean copyOnWrite = false;

  29.     /** List of parameters. */
  30.     private Map<ParameterType<?>, Object> parameters;

  31.     /** List of parameters with values before last set. */
  32.     private Map<ParameterType<?>, Object> previous;

  33.     /**
  34.      * Construct a new, empty Parameters set.
  35.      */
  36.     public ParameterSet()
  37.     {
  38.         this.parameters = new LinkedHashMap<>();
  39.         this.previous = new LinkedHashMap<>();
  40.     }

  41.     /**
  42.      * Constructor which creates a copy of the input set.
  43.      * @param parameters Parameters; input set to copy into the new Parameters object
  44.      */
  45.     public ParameterSet(final Parameters parameters)
  46.     {
  47.         if (parameters instanceof ParameterSet)
  48.         {
  49.             ParameterSet parameterSet = (ParameterSet) parameters;
  50.             this.parameters = parameterSet.parameters;
  51.             this.previous = parameterSet.previous;
  52.             this.copyOnWrite = true;
  53.             parameterSet.copyOnWrite = true;
  54.         }
  55.         else
  56.         {
  57.             parameters.setAllIn(this);
  58.         }
  59.     }

  60.     /** {@inheritDoc} */
  61.     @Override
  62.     public final <T> void setParameter(final ParameterType<T> parameterType, final T value) throws ParameterException
  63.     {
  64.         Throw.when(value == null, ParameterException.class,
  65.                 "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
  66.         saveSetParameter(parameterType, value, false);
  67.     }

  68.     /** {@inheritDoc} */
  69.     @Override
  70.     public final <T> void setParameterResettable(final ParameterType<T> parameterType, final T value) throws ParameterException
  71.     {
  72.         Throw.when(value == null, ParameterException.class,
  73.                 "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
  74.         saveSetParameter(parameterType, value, true);
  75.     }

  76.     /**
  77.      * Sets a parameter value while checking conditions.
  78.      * @param parameterType ParameterType&lt;T&gt;; the parameter type
  79.      * @param value T; new value for the parameter
  80.      * @param resettable boolean; whether the parameter set should be resettable
  81.      * @param <T> Class of the value
  82.      * @throws ParameterException If the value does not comply with constraints.
  83.      */
  84.     private <T> void saveSetParameter(final ParameterType<T> parameterType, final T value, final boolean resettable)
  85.             throws ParameterException
  86.     {
  87.         parameterType.check(value, this);
  88.         parameterType.checkConstraint(value);
  89.         checkCopyOnWrite();
  90.         if (resettable)
  91.         {
  92.             Object prevValue = this.parameters.get(parameterType);
  93.             if (prevValue == null)
  94.             {
  95.                 // remember that there was no value before this set
  96.                 this.previous.put(parameterType, EMPTY);
  97.             }
  98.             else
  99.             {
  100.                 this.previous.put(parameterType, prevValue);
  101.             }
  102.         }
  103.         else
  104.         {
  105.             // no reset after non-resettale set
  106.             this.previous.remove(parameterType);
  107.         }
  108.         this.parameters.put(parameterType, value);
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public final void resetParameter(final ParameterType<?> parameterType) throws ParameterException
  113.     {
  114.         checkCopyOnWrite();
  115.         Object prevValue = this.previous.remove(parameterType);
  116.         Throw.when(prevValue == null, ParameterException.class,
  117.                 "Reset on parameter of type '%s' could not be performed, it was not set resettable.", parameterType.getId());
  118.         if (prevValue instanceof Empty)
  119.         {
  120.             // no value was set before last set, so make parameter type not set
  121.             this.parameters.remove(parameterType);
  122.         }
  123.         else
  124.         {
  125.             this.parameters.put(parameterType, prevValue);
  126.         }
  127.     }

  128.     /**
  129.      * Copy the internal data if needed.
  130.      */
  131.     private void checkCopyOnWrite()
  132.     {
  133.         if (this.copyOnWrite)
  134.         {
  135.             this.parameters = new LinkedHashMap<>(this.parameters);
  136.             this.previous = new LinkedHashMap<>(this.previous);
  137.             this.copyOnWrite = false;
  138.         }
  139.     }

  140.     /** {@inheritDoc} */
  141.     @Override
  142.     @SuppressWarnings("checkstyle:designforextension")
  143.     public <T> T getParameter(final ParameterType<T> parameterType) throws ParameterException
  144.     {
  145.         @SuppressWarnings("unchecked")
  146.         // set methods guarantee matching of parameter type and value
  147.         T result = (T) this.parameters.get(parameterType);
  148.         Throw.when(result == null, ParameterException.class, "Could not get parameter of type '%s' as it was not set.",
  149.                 parameterType.getId());
  150.         return result;
  151.     }

  152.     /** {@inheritDoc} */
  153.     @Override
  154.     @SuppressWarnings("unchecked")
  155.     public final <T> T getParameterOrNull(final ParameterType<T> parameterType)
  156.     {
  157.         // set methods guarantee matching of parameter type and value
  158.         return (T) this.parameters.get(parameterType);
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     public final boolean contains(final ParameterType<?> parameterType)
  163.     {
  164.         return this.parameters.containsKey(parameterType);
  165.     }

  166.     /**
  167.      * Returns a safe copy of the parameters.
  168.      * @return Map&lt;AbstractParameterType&lt;?&gt;&gt;; a safe copy of the parameters, e.g., for printing
  169.      */
  170.     public final Map<ParameterType<?>, Object> getParameters()
  171.     {
  172.         return new LinkedHashMap<>(this.parameters);
  173.     }

  174.     /**
  175.      * Sets the default value of a parameter. Default value sets are not resettable.
  176.      * @param parameter ParameterType&lt;T&gt;; the parameter to set the default value of
  177.      * @param <T> Class of the value
  178.      * @return Parameters; this set of parameters (for method chaining)
  179.      * @throws ParameterException if the parameter type has no default value
  180.      */
  181.     public final <T> ParameterSet setDefaultParameter(final ParameterType<T> parameter) throws ParameterException
  182.     {
  183.         T defaultValue = parameter.getDefaultValue();
  184.         try
  185.         {
  186.             saveSetParameter(parameter, defaultValue, false);
  187.         }
  188.         catch (ParameterException pe)
  189.         {
  190.             // should not happen, default value and parameter type are connected
  191.             throw new RuntimeException(pe);
  192.         }
  193.         return this;
  194.     }

  195.     /**
  196.      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not
  197.      * resettable.<br>
  198.      * @param clazz Class&lt;?&gt;; class with parameters
  199.      * @return Parameters; this set of parameters (for method chaining)
  200.      */
  201.     public final ParameterSet setDefaultParameters(final Class<?> clazz)
  202.     {
  203.         return setDefaultParametersLocal(clazz);
  204.     }

  205.     /**
  206.      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not resettable.
  207.      * @param clazz Class&lt;?&gt;; class with parameters
  208.      * @param <T> Class of the value
  209.      * @return this set of parameters (for method chaining)
  210.      */
  211.     @SuppressWarnings("unchecked")
  212.     private <T> ParameterSet setDefaultParametersLocal(final Class<?> clazz)
  213.     {
  214.         // set all default values using reflection
  215.         Set<Field> fields = ClassUtil.getAllFields(clazz);

  216.         for (Field field : fields)
  217.         {
  218.             if (ParameterType.class.isAssignableFrom(field.getType()))
  219.             {
  220.                 try
  221.                 {
  222.                     field.setAccessible(true);
  223.                     ParameterType<T> p = (ParameterType<T>) field.get(clazz);
  224.                     saveSetParameter(p, p.getDefaultValue(), false);
  225.                 }
  226.                 catch (IllegalArgumentException iare)
  227.                 {
  228.                     // should not happen, field and clazz are related
  229.                     throw new RuntimeException(iare);
  230.                 }
  231.                 catch (IllegalAccessException iace)
  232.                 {
  233.                     // parameter type not public
  234.                     throw new RuntimeException(iace);
  235.                 }
  236.                 catch (ParameterException pe)
  237.                 {
  238.                     // do not set parameter without default value
  239.                     throw new RuntimeException(pe);
  240.                 }
  241.             }
  242.         }
  243.         return this;
  244.     }

  245.     /** {@inheritDoc} */
  246.     @Override
  247.     public final void setAllIn(final Parameters params)
  248.     {
  249.         if (params instanceof ParameterSet)
  250.         {
  251.             ParameterSet parameterSet = (ParameterSet) params;
  252.             parameterSet.checkCopyOnWrite();
  253.             parameterSet.parameters.putAll(this.parameters);
  254.         }
  255.         else
  256.         {
  257.             setAllOneByOne(params);
  258.         }
  259.     }

  260.     /**
  261.      * Sets the parameters of this set in the given set.
  262.      * @param params Parameters; parameters to set the values in
  263.      * @param <T> parameter value type
  264.      */
  265.     @SuppressWarnings("unchecked")
  266.     private <T> void setAllOneByOne(final Parameters params)
  267.     {
  268.         for (ParameterType<?> parameterType : this.parameters.keySet())
  269.         {
  270.             try
  271.             {
  272.                 params.setParameter((ParameterType<T>) parameterType, (T) this.parameters.get(parameterType));
  273.             }
  274.             catch (ParameterException exception)
  275.             {
  276.                 throw new RuntimeException(exception); // should not happen
  277.             }
  278.         }
  279.     }

  280.     /** {@inheritDoc} */
  281.     @Override
  282.     public final String toString()
  283.     {
  284.         StringBuilder out = new StringBuilder("Parameters [");
  285.         String sep = "";
  286.         for (ParameterType<?> apt : this.parameters.keySet())
  287.         {
  288.             try
  289.             {
  290.                 out.append(sep).append(apt.getId()).append("=").append(apt.printValue(this));
  291.                 sep = ", ";
  292.             }
  293.             catch (ParameterException pe)
  294.             {
  295.                 // We know the parameter has been set as we get the keySet from parameters
  296.                 throw new RuntimeException(pe);
  297.             }
  298.         }
  299.         out.append("]");
  300.         return out.toString();
  301.     }

  302.     /**
  303.      * Class of object to put in the internal Map of Parameters to indicate that no value was set.
  304.      */
  305.     private static class Empty extends Dimensionless
  306.     {
  307.         /** */
  308.         private static final long serialVersionUID = 20160414L;

  309.         /**
  310.          * Constructor for Empty.
  311.          */
  312.         Empty()
  313.         {
  314.             super(Double.NaN, DimensionlessUnit.SI);
  315.         }
  316.     }

  317. }