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 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      @Override
71      public final <T> void setParameter(final ParameterType<T> parameterType, final T value) throws ParameterException
72      {
73          Throw.when(value == null, ParameterException.class,
74                  "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
75          saveSetParameter(parameterType, value, false);
76      }
77  
78      @Override
79      public final <T> void setParameterResettable(final ParameterType<T> parameterType, final T value) throws ParameterException
80      {
81          Throw.when(value == null, ParameterException.class,
82                  "Parameter of type '%s' was assigned a null value, this is not allowed.", parameterType.getId());
83          saveSetParameter(parameterType, value, true);
84      }
85  
86      /**
87       * Sets a parameter value while checking conditions.
88       * @param parameterType the parameter type
89       * @param value new value for the parameter
90       * @param resettable whether the parameter set should be resettable
91       * @param <T> Class of the value
92       * @throws ParameterException If the value does not comply with constraints.
93       */
94      private <T> void saveSetParameter(final ParameterType<T> parameterType, final T value, final boolean resettable)
95              throws ParameterException
96      {
97          parameterType.check(value, this);
98          parameterType.checkConstraint(value);
99          checkCopyOnWrite();
100         if (resettable)
101         {
102             Object prevValue = this.parameters.get(parameterType);
103             if (prevValue == null)
104             {
105                 // remember that there was no value before this set
106                 this.previous.put(parameterType, EMPTY);
107             }
108             else
109             {
110                 this.previous.put(parameterType, prevValue);
111             }
112         }
113         else
114         {
115             // no reset after non-resettale set
116             this.previous.remove(parameterType);
117         }
118         this.parameters.put(parameterType, value);
119     }
120 
121     @Override
122     public final void resetParameter(final ParameterType<?> parameterType) throws ParameterException
123     {
124         checkCopyOnWrite();
125         Object prevValue = this.previous.remove(parameterType);
126         Throw.when(prevValue == null, ParameterException.class,
127                 "Reset on parameter of type '%s' could not be performed, it was not set resettable.", parameterType.getId());
128         if (prevValue instanceof Empty)
129         {
130             // no value was set before last set, so make parameter type not set
131             this.parameters.remove(parameterType);
132         }
133         else
134         {
135             this.parameters.put(parameterType, prevValue);
136         }
137     }
138 
139     /**
140      * Copy the internal data if needed.
141      */
142     private void checkCopyOnWrite()
143     {
144         if (this.copyOnWrite)
145         {
146             this.parameters = new LinkedHashMap<>(this.parameters);
147             this.previous = new LinkedHashMap<>(this.previous);
148             this.copyOnWrite = false;
149         }
150     }
151 
152     @Override
153     public final <T> T getParameter(final ParameterType<T> parameterType) throws ParameterException
154     {
155         @SuppressWarnings("unchecked")
156         // set methods guarantee matching of parameter type and value
157         T result = (T) this.parameters.get(parameterType);
158         Throw.when(result == null, ParameterException.class, "Could not get parameter of type '%s' as it was not set.",
159                 parameterType.getId());
160         return result;
161     }
162 
163     @Override
164     @SuppressWarnings("unchecked")
165     public final <T> T getParameterOrNull(final ParameterType<T> parameterType)
166     {
167         // set methods guarantee matching of parameter type and value
168         return (T) this.parameters.get(parameterType);
169     }
170 
171     @Override
172     public final boolean contains(final ParameterType<?> parameterType)
173     {
174         return this.parameters.containsKey(parameterType);
175     }
176 
177     /**
178      * Returns a safe copy of the parameters.
179      * @return a safe copy of the parameters, e.g., for printing
180      */
181     public final Map<ParameterType<?>, Object> getParameters()
182     {
183         return new LinkedHashMap<>(this.parameters);
184     }
185 
186     /**
187      * Sets the default value of a parameter. Default value sets are not resettable.
188      * @param parameter the parameter to set the default value of
189      * @param <T> Class of the value
190      * @return this set of parameters (for method chaining)
191      * @throws ParameterException if the parameter type has no default value
192      */
193     public final <T> ParameterSet setDefaultParameter(final ParameterType<T> parameter) throws ParameterException
194     {
195         T defaultValue = parameter.getDefaultValue();
196         try
197         {
198             saveSetParameter(parameter, defaultValue, false);
199         }
200         catch (ParameterException pe)
201         {
202             // should not happen, default value and parameter type are connected
203             throw new RuntimeException(pe);
204         }
205         return this;
206     }
207 
208     /**
209      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not
210      * resettable.<br>
211      * @param clazz class with parameters
212      * @return this set of parameters (for method chaining)
213      */
214     public final ParameterSet setDefaultParameters(final Class<?> clazz)
215     {
216         return setDefaultParametersLocal(clazz);
217     }
218 
219     /**
220      * Sets the default values of all accessible parameters defined in the given class. Default value sets are not resettable.
221      * @param clazz class with parameters
222      * @param <T> Class of the value
223      * @return this set of parameters (for method chaining)
224      */
225     @SuppressWarnings("unchecked")
226     private <T> ParameterSet setDefaultParametersLocal(final Class<?> clazz)
227     {
228         // set all default values using reflection
229         Set<Field> fields = ClassUtil.getAllFields(clazz);
230 
231         for (Field field : fields)
232         {
233             if (ParameterType.class.isAssignableFrom(field.getType()))
234             {
235                 try
236                 {
237                     field.setAccessible(true);
238                     ParameterType<T> p = (ParameterType<T>) field.get(clazz);
239                     saveSetParameter(p, p.getDefaultValue(), false);
240                 }
241                 catch (IllegalArgumentException iare)
242                 {
243                     // should not happen, field and clazz are related
244                     throw new RuntimeException(iare);
245                 }
246                 catch (IllegalAccessException iace)
247                 {
248                     // parameter type not public
249                     throw new RuntimeException(iace);
250                 }
251                 catch (ParameterException pe)
252                 {
253                     // do not set parameter without default value
254                     throw new RuntimeException(pe);
255                 }
256             }
257         }
258         return this;
259     }
260 
261     @Override
262     public final void setAllIn(final Parameters params)
263     {
264         if (params instanceof ParameterSet)
265         {
266             ParameterSet parameterSet = (ParameterSet) params;
267             parameterSet.checkCopyOnWrite();
268             parameterSet.parameters.putAll(this.parameters);
269         }
270         else
271         {
272             setAllOneByOne(params);
273         }
274     }
275 
276     /**
277      * Sets the parameters of this set in the given set.
278      * @param params parameters to set the values in
279      * @param <T> parameter value type
280      */
281     @SuppressWarnings("unchecked")
282     private <T> void setAllOneByOne(final Parameters params)
283     {
284         for (ParameterType<?> parameterType : this.parameters.keySet())
285         {
286             try
287             {
288                 params.setParameter((ParameterType<T>) parameterType, (T) this.parameters.get(parameterType));
289             }
290             catch (ParameterException exception)
291             {
292                 throw new RuntimeException(exception); // should not happen
293             }
294         }
295     }
296 
297     @Override
298     public final String toString()
299     {
300         StringBuilder out = new StringBuilder("Parameters [");
301         String sep = "";
302         for (ParameterType<?> apt : this.parameters.keySet())
303         {
304             try
305             {
306                 out.append(sep).append(apt.getId()).append("=").append(apt.printValue(this));
307                 sep = ", ";
308             }
309             catch (ParameterException pe)
310             {
311                 // We know the parameter has been set as we get the keySet from parameters
312                 throw new RuntimeException(pe);
313             }
314         }
315         out.append("]");
316         return out.toString();
317     }
318 
319     /**
320      * Class of object to put in the internal Map of Parameters to indicate that no value was set.
321      */
322     private static class Empty extends Dimensionless
323     {
324         /** */
325         private static final long serialVersionUID = 20160414L;
326 
327         /**
328          * Constructor for Empty.
329          */
330         Empty()
331         {
332             super(Double.NaN, DimensionlessUnit.SI);
333         }
334 
335         @Override
336         public String toString()
337         {
338             return "Empty []";
339         }
340 
341     }
342 
343 }