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