View Javadoc
1   package org.opentrafficsim.base.parameters;
2   
3   import java.io.Serializable;
4   import java.lang.reflect.Field;
5   import java.util.HashMap;
6   import java.util.Map;
7   import java.util.Set;
8   
9   import org.djunits.unit.DimensionlessUnit;
10  import org.djunits.value.vdouble.scalar.Dimensionless;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.reflection.ClassUtil;
13  
14  /**
15   * Implementation of {@link Parameters} with methods to initialize the set of parameters.
16   * <p>
17   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
19   * <p>
20   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 13, 2016 <br>
21   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
23   */
24  public class ParameterSet implements Parameters, Serializable
25  {
26  
27      /** */
28      private static final long serialVersionUID = 20160400L;
29  
30      /** Object to recognize that no value was set previously. */
31      private static final Empty EMPTY = new Empty();
32  
33      /** Whether to copy internal data on write. */
34      private boolean copyOnWrite = false;
35  
36      /** List of parameters. */
37      private Map<ParameterType<?>, Object> parameters;
38  
39      /** List of parameters with values before last set. */
40      private Map<ParameterType<?>, Object> previous;
41  
42      /**
43       * Construct a new, empty Parameters set.
44       */
45      public ParameterSet()
46      {
47          this.parameters = new HashMap<>();
48          this.previous = new HashMap<>();
49      }
50  
51      /**
52       * Constructor which creates a copy of the input set.
53       * @param parameters Parameters; input set to copy into the new Parameters object
54       */
55      public ParameterSet(final Parameters parameters)
56      {
57          if (parameters instanceof ParameterSet)
58          {
59              ParameterSet parameterSet = (ParameterSet) parameters;
60              this.parameters = parameterSet.parameters;
61              this.previous = parameterSet.previous;
62              this.copyOnWrite = true;
63              parameterSet.copyOnWrite = true;
64          }
65          else
66          {
67              parameters.setAllIn(this);
68          }
69      }
70  
71      /** {@inheritDoc} */
72      @Override
73      public final <T> void setParameter(final ParameterType<T> parameterType, final T value) throws ParameterException
74      {
75          Throw.when(value == null, ParameterException.class,
76                  "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
77          saveSetParameter(parameterType, value, false);
78      }
79  
80      /** {@inheritDoc} */
81      @Override
82      public final <T> void setParameterResettable(final ParameterType<T> parameterType, final T value) throws ParameterException
83      {
84          Throw.when(value == null, ParameterException.class,
85                  "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
86          saveSetParameter(parameterType, value, true);
87      }
88  
89      /**
90       * Sets a parameter value while checking conditions.
91       * @param parameterType ParameterType&lt;T&gt;; the parameter type
92       * @param value T; new value for the parameter
93       * @param resettable boolean; whether the parameter set should be resettable
94       * @param <T> Class of the value
95       * @throws ParameterException If the value does not comply with constraints.
96       */
97      private <T> void saveSetParameter(final ParameterType<T> parameterType, final T value, final boolean resettable)
98              throws ParameterException
99      {
100         parameterType.check(value, this);
101         parameterType.checkConstraint(value);
102         checkCopyOnWrite();
103         if (resettable)
104         {
105             Object prevValue = this.parameters.get(parameterType);
106             if (prevValue == null)
107             {
108                 // remember that there was no value before this set
109                 this.previous.put(parameterType, EMPTY);
110             }
111             else
112             {
113                 this.previous.put(parameterType, prevValue);
114             }
115         }
116         else
117         {
118             // no reset after non-resettale set
119             this.previous.remove(parameterType);
120         }
121         this.parameters.put(parameterType, value);
122     }
123 
124     /** {@inheritDoc} */
125     @Override
126     public final void resetParameter(final ParameterType<?> parameterType) throws ParameterException
127     {
128         checkCopyOnWrite();
129         Object prevValue = this.previous.remove(parameterType);
130         Throw.when(prevValue == null, ParameterException.class,
131                 "Reset on parameter of type '%s' could not be performed, it was not set resettable.", parameterType.getId());
132         if (prevValue instanceof Empty)
133         {
134             // no value was set before last set, so make parameter type not set
135             this.parameters.remove(parameterType);
136         }
137         else
138         {
139             this.parameters.put(parameterType, prevValue);
140         }
141     }
142 
143     /**
144      * Copy the internal data if needed.
145      */
146     private void checkCopyOnWrite()
147     {
148         if (this.copyOnWrite)
149         {
150             this.parameters = new HashMap<>(this.parameters);
151             this.previous = new HashMap<>(this.previous);
152             this.copyOnWrite = false;
153         }
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     @SuppressWarnings("checkstyle:designforextension")
159     public <T> T getParameter(final ParameterType<T> parameterType) throws ParameterException
160     {
161         @SuppressWarnings("unchecked")
162         // set methods guarantee matching of parameter type and value
163         T result = (T) this.parameters.get(parameterType);
164         Throw.when(result == null, ParameterException.class, "Could not get parameter of type '%s' as it was not set.",
165                 parameterType.getId());
166         return result;
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     @SuppressWarnings("unchecked")
172     public final <T> T getParameterOrNull(final ParameterType<T> parameterType)
173     {
174         // set methods guarantee matching of parameter type and value
175         return (T) this.parameters.get(parameterType);
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     public final boolean contains(final ParameterType<?> parameterType)
181     {
182         return this.parameters.containsKey(parameterType);
183     }
184 
185     /**
186      * Returns a safe copy of the parameters.
187      * @return Map&lt;AbstractParameterType&lt;?&gt;&gt;; a safe copy of the parameters, e.g., for printing
188      */
189     public final Map<ParameterType<?>, Object> getParameters()
190     {
191         return new HashMap<>(this.parameters);
192     }
193 
194     /**
195      * Sets the default value of a parameter. Default value sets are not resettable.
196      * @param parameter ParameterType&lt;T&gt;; the parameter to set the default value of
197      * @param <T> Class of the value
198      * @return Parameters; this set of parameters (for method chaining)
199      * @throws ParameterException if the parameter type has no default value
200      */
201     public final <T> ParameterSet setDefaultParameter(final ParameterType<T> parameter) throws ParameterException
202     {
203         T defaultValue = parameter.getDefaultValue();
204         try
205         {
206             saveSetParameter(parameter, defaultValue, false);
207         }
208         catch (ParameterException pe)
209         {
210             // should not happen, default value and parameter type are connected
211             throw new RuntimeException(pe);
212         }
213         return this;
214     }
215 
216     /**
217      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not
218      * resettable.<br>
219      * @param clazz Class&lt;?&gt;; class with parameters
220      * @return Parameters; this set of parameters (for method chaining)
221      */
222     public final ParameterSet setDefaultParameters(final Class<?> clazz)
223     {
224         return setDefaultParametersLocal(clazz);
225     }
226 
227     /**
228      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not resettable.
229      * @param clazz Class&lt;?&gt;; class with parameters
230      * @param <T> Class of the value
231      * @return this set of parameters (for method chaining)
232      */
233     @SuppressWarnings("unchecked")
234     private <T> ParameterSet setDefaultParametersLocal(final Class<?> clazz)
235     {
236         // set all default values using reflection
237         Set<Field> fields = ClassUtil.getAllFields(clazz);
238 
239         for (Field field : fields)
240         {
241             if (ParameterType.class.isAssignableFrom(field.getType()))
242             {
243                 try
244                 {
245                     field.setAccessible(true);
246                     ParameterType<T> p = (ParameterType<T>) field.get(clazz);
247                     saveSetParameter(p, p.getDefaultValue(), false);
248                 }
249                 catch (IllegalArgumentException iare)
250                 {
251                     // should not happen, field and clazz are related
252                     throw new RuntimeException(iare);
253                 }
254                 catch (IllegalAccessException iace)
255                 {
256                     // parameter type not public
257                     throw new RuntimeException(iace);
258                 }
259                 catch (ParameterException pe)
260                 {
261                     // do not set parameter without default value
262                     throw new RuntimeException(pe);
263                 }
264             }
265         }
266         return this;
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     public final void setAllIn(final Parameters params)
272     {
273         if (params instanceof ParameterSet)
274         {
275             ParameterSet parameterSet = (ParameterSet) params;
276             parameterSet.checkCopyOnWrite();
277             parameterSet.parameters.putAll(this.parameters);
278         }
279         else
280         {
281             setAllOneByOne(params);
282         }
283     }
284 
285     /**
286      * Sets the parameters of this set in the given set.
287      * @param params Parameters; parameters to set the values in
288      * @param <T> parameter value type
289      */
290     @SuppressWarnings("unchecked")
291     private <T> void setAllOneByOne(final Parameters params)
292     {
293         for (ParameterType<?> parameterType : this.parameters.keySet())
294         {
295             try
296             {
297                 params.setParameter((ParameterType<T>) parameterType, (T) this.parameters.get(parameterType));
298             }
299             catch (ParameterException exception)
300             {
301                 throw new RuntimeException(exception); // should not happen
302             }
303         }
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public final String toString()
309     {
310         StringBuilder out = new StringBuilder("Parameters [");
311         String sep = "";
312         for (ParameterType<?> apt : this.parameters.keySet())
313         {
314             try
315             {
316                 out.append(sep).append(apt.getId()).append("=").append(apt.printValue(this));
317                 sep = ", ";
318             }
319             catch (ParameterException pe)
320             {
321                 // We know the parameter has been set as we get the keySet from parameters
322                 throw new RuntimeException(pe);
323             }
324         }
325         out.append("]");
326         return out.toString();
327     }
328 
329     /**
330      * Class of object to put in the internal Map of Parameters to indicate that no value was set.
331      */
332     private static class Empty extends Dimensionless
333     {
334         /** */
335         private static final long serialVersionUID = 20160414L;
336 
337         /**
338          * Constructor for Empty.
339          */
340         Empty()
341         {
342             super(Double.NaN, DimensionlessUnit.SI);
343         }
344     }
345 
346 }