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