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