View Javadoc
1   package org.djunits.value;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.fail;
5   
6   import java.lang.reflect.Constructor;
7   import java.lang.reflect.Field;
8   import java.lang.reflect.InvocationTargetException;
9   import java.lang.reflect.Method;
10  
11  import org.djunits.unit.Unit;
12  import org.djunits.unit.quantity.Quantity;
13  import org.djunits.unit.scale.LinearScale;
14  import org.djunits.unit.si.SIPrefixes;
15  import org.djunits.unit.unitsystem.UnitSystem;
16  import org.djunits.util.ClassUtil;
17  import org.djunits.value.vdouble.scalar.base.AbstractDoubleScalar;
18  import org.djunits.value.vdouble.scalar.base.AbstractDoubleScalarAbs;
19  import org.djunits.value.vdouble.scalar.base.AbstractDoubleScalarRel;
20  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
21  import org.djunits.value.vdouble.scalar.base.DoubleScalarInterface;
22  import org.djunits.value.vfloat.scalar.base.AbstractFloatScalar;
23  import org.djunits.value.vfloat.scalar.base.AbstractFloatScalarAbs;
24  import org.djunits.value.vfloat.scalar.base.AbstractFloatScalarRel;
25  import org.djunits.value.vfloat.scalar.base.FloatScalar;
26  import org.djunits.value.vfloat.scalar.base.FloatScalarInterface;
27  import org.junit.Assert;
28  import org.junit.Test;
29  
30  /**
31   * Find all plus, minus, times and divide operations and prove the type correctness.
32   * <p>
33   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
34   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
35   * </p>
36   * version Sep 14, 2015 <br>
37   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
38   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
39   */
40  public class ScalarOperationsTest
41  {
42      /**
43       * Test constructor on the specified double scalar classes.
44       * @throws IllegalAccessException on class or method resolving error
45       * @throws InstantiationException on class or method resolving error
46       * @throws NoSuchMethodException on class or method resolving error
47       * @throws InvocationTargetException on class or method resolving error
48       * @throws NoSuchFieldException on class or method resolving error
49       * @throws ClassNotFoundException on reflection error
50       * @throws IllegalArgumentException on reflection error
51       * @throws SecurityException on reflection error
52       */
53      @Test
54      public final void scalarOperationsTest() throws NoSuchMethodException, InstantiationException, IllegalAccessException,
55              InvocationTargetException, NoSuchFieldException, SecurityException, IllegalArgumentException, ClassNotFoundException
56      {
57          doubleOrFloatScalarOperationsTest(true); // Double precision versions
58          doubleOrFloatScalarOperationsTest(false); // Float versions
59      }
60  
61      /**
62       * Perform many tests on scalar types.
63       * @param doubleType boolean; if true; perform tests on DoubleScalar types; if false; perform tests on FloatScalar types
64       * @throws NoSuchFieldException on class or method resolving error
65       * @throws InvocationTargetException on class or method resolving error
66       * @throws IllegalAccessException on class or method resolving error
67       * @throws InstantiationException on class or method resolving error
68       * @throws NoSuchMethodException on class or method resolving error
69       * @throws ClassNotFoundException on reflection error
70       * @throws IllegalArgumentException on reflection error
71       * @throws SecurityException on reflection error
72       */
73      private void doubleOrFloatScalarOperationsTest(final boolean doubleType)
74              throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
75              NoSuchFieldException, SecurityException, IllegalArgumentException, ClassNotFoundException
76      {
77          final String upperType = doubleType ? "Double" : "Float";
78          final String type = upperType.toLowerCase();
79          // get the interfaces such as org.djunits.value.vdouble.scalar.Time
80          for (int i = 0; i < CLASSNAMES.ABS_LIST.size(); i++)
81          {
82              String scalarNameAbs = CLASSNAMES.ABS_LIST.get(i);
83              String scalarNameRel = CLASSNAMES.REL_WITH_ABS_LIST.get(i);
84              String scalarClassNameAbs = doubleType ? scalarNameAbs : "Float" + scalarNameAbs;
85              String scalarClassNameRel = doubleType ? scalarNameRel : "Float" + scalarNameRel;
86              Class<?> scalarClassAbs = null;
87              Class<?> scalarClassRel = null;
88              // get the subClassName implementation of that class
89              try
90              {
91                  scalarClassAbs = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassNameAbs);
92              }
93              catch (ClassNotFoundException exception)
94              {
95                  fail("Class Rel not found for " + upperType + "Scalar class " + "org.djunits.value.v" + type + ".scalar."
96                          + scalarClassNameAbs);
97              }
98              try
99              {
100                 scalarClassRel = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassNameRel);
101             }
102             catch (ClassNotFoundException exception)
103             {
104                 fail("Class Rel not found for " + upperType + "Scalar class " + "org.djunits.value.v" + type + ".scalar."
105                         + scalarClassNameRel);
106             }
107             testMethods(scalarClassAbs, true, doubleType);
108             testMethods(scalarClassRel, false, doubleType);
109         }
110 
111         // get the interfaces such as org.djunits.value.vXXXX.scalar.Area
112         for (String scalarName : CLASSNAMES.REL_LIST)
113         {
114             String scalarClassName = doubleType ? scalarName : "Float" + scalarName;
115             Class<?> scalarClassRel = null;
116             try
117             {
118                 scalarClassRel = Class.forName("org.djunits.value.v" + type + ".scalar." + scalarClassName);
119             }
120             catch (ClassNotFoundException exception)
121             {
122                 fail("Class Rel not found for " + upperType + "DoubleScalar class " + "org.djunits.value.v" + type + ".scalar."
123                         + scalarClassName);
124             }
125             testMethods(scalarClassRel, false, doubleType);
126         }
127     }
128 
129     /**
130      * Find the methods defined in the class itself (not in a superclass) called times or divide and test the method. Also test
131      * the Unary methods of the class.
132      * @param scalarClassAbsRel class to test
133      * @param isAbs boolean; if true; the scalarClassAbsRel must be aAsolute; if false; the scalarClassAbsRel must be Relative
134      * @param doubleType boolean; if true; perform tests on DoubleScalar; if false perform tests on FloatScalar
135      * @throws InvocationTargetException on class or method resolving error
136      * @throws IllegalAccessException on class or method resolving error
137      * @throws InstantiationException on class or method resolving error
138      * @throws NoSuchMethodException on class or method resolving error
139      * @throws NoSuchFieldException on class or method resolving error
140      * @throws ClassNotFoundException on reflection error
141      */
142     private void testMethods(final Class<?> scalarClassAbsRel, final boolean isAbs, final boolean doubleType)
143             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
144             NoSuchFieldException, ClassNotFoundException
145     {
146         for (Method method : scalarClassAbsRel.getMethods())
147         {
148             if (method.getName().equals("times"))
149             {
150                 // note: filter out the method that multiplies by a constant or a general scalar...
151                 testMultiplyOrDivideMethodAbsRel(scalarClassAbsRel, isAbs, method, true, doubleType);
152             }
153             else if (method.getName().equals("divide"))
154             {
155                 // note: filter out the method that divides by a constant or a general scalar...
156                 testMultiplyOrDivideMethodAbsRel(scalarClassAbsRel, isAbs, method, false, doubleType);
157             }
158         }
159         testUnaryMethods(scalarClassAbsRel, isAbs, doubleType);
160         testStaticMethods(scalarClassAbsRel, isAbs, doubleType);
161     }
162 
163     /**
164      * Test a multiplication method for an Abs or Rel scalar. Note: filter out the method that multiplies by a constant...
165      * @param scalarClass the Abs or Rel class for the multiplication, e.g. Length
166      * @param abs boolean; true to test the Abs sub-class; false to test the Rel sub-class
167      * @param method the method 'times' for that class
168      * @param multiply boolean; if true; test a times method; if false; test a divide method
169      * @param doubleType boolean; if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
170      * @throws NoSuchMethodException on class or method resolving error
171      * @throws InvocationTargetException on class or method resolving error
172      * @throws IllegalAccessException on class or method resolving error
173      * @throws InstantiationException on class or method resolving error
174      * @throws NoSuchFieldException on class or method resolving error
175      */
176     private void testMultiplyOrDivideMethodAbsRel(final Class<?> scalarClass, final boolean abs, final Method method,
177             final boolean multiply, final boolean doubleType) throws NoSuchMethodException, InstantiationException,
178             IllegalAccessException, InvocationTargetException, NoSuchFieldException
179     {
180         Class<?> relativeOrAbsoluteClass = null;
181         try
182         {
183             relativeOrAbsoluteClass = Class.forName("org.djunits.value." + (abs ? "Absolute" : "Relative"));
184         }
185         catch (ClassNotFoundException exception)
186         {
187             fail("Could not find org.djunits.value.Relative class");
188         }
189         Class<?>[] parTypes = method.getParameterTypes();
190         if (parTypes.length != 1)
191         {
192             fail("DoubleScalar class " + scalarClass.getName() + "." + method.getName() + "() has " + parTypes.length
193                     + " parameters, <> 1");
194         }
195         Class<?> parameterClass = parTypes[0];
196         if (parameterClass.toString().equals("double") || parameterClass.toString().equals("float"))
197         {
198             // not interested in multiplying a scalar with a double.
199             return;
200         }
201         if (parameterClass.getSimpleName().startsWith("Abstract"))
202         {
203             // not interested in multiplying a scalar with a generic scalar.
204             return;
205         }
206         if (!relativeOrAbsoluteClass.isAssignableFrom(parameterClass))
207         {
208             // system.out.println("abs=" + abs + ", method=" + scalarClass.getName() + "." + method.getName() + " param="
209             // + parameterClass.getName());
210             Assert.fail("DoubleScalar class " + scalarClass.getName() + "." + method.getName() + "() has parameter with non-"
211                     + relativeOrAbsoluteClass + " class: " + relativeOrAbsoluteClass.getName());
212         }
213 
214         Class<?> returnClass = method.getReturnType();
215         if (!relativeOrAbsoluteClass.isAssignableFrom(returnClass))
216         {
217             Assert.fail("DoubleScalar class " + scalarClass.getName() + ".times() has return type with non-relative class: "
218                     + returnClass.getName());
219         }
220 
221         // get the SI coefficients of the unit classes, scalar type, parameter type and return type
222         String returnSI = getCoefficients(getUnitClass(returnClass));
223         // String scalarSI = getCoefficients(getUnitClass(scalarClass));
224         // String paramSI = getCoefficients(getUnitClass(parameterClass));
225         // print what we just have found
226         // system.out.println(scalarClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + "."
227         // + (multiply ? "times" : "divide") + "("
228         // + parameterClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + ") => "
229         // + returnClass.getName().replaceFirst("org.djunits.value.vdouble.scalar.", "") + ": " + scalarSI
230         // + (multiply ? " * " : " : ") + paramSI + " => " + returnSI);
231 
232         Constructor<?> constructor = scalarClass.getConstructor(double.class, getUnitClass(scalarClass));
233         if (abs)
234         {
235             fail("Absolute types should not have a multiply or divide method");
236             AbstractDoubleScalarAbs<?, ?, ?, ?> left = (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(123d,
237                     getSIUnitInstance(getUnitClass(scalarClass), abs));
238             // System.out.println("constructed left: " + left);
239             constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
240             AbstractDoubleScalarAbs<?, ?, ?, ?> right = (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(456d,
241                     getSIUnitInstance(getUnitClass(parameterClass), abs));
242             // System.out.println("constructed right: " + right);
243             double expectedValue = multiply ? 123d * 456 : 123d / 456;
244 
245             if (multiply)
246             {
247                 Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
248                 Object result = multiplyMethod.invoke(left, right);
249                 double resultSI = ((AbstractDoubleScalarAbs<?, ?, ?, ?>) result).getSI();
250                 assertEquals("Result of operation", expectedValue, resultSI, 0.01);
251             }
252             else
253             {
254                 Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
255                 Object result = divideMethod.invoke(left, right);
256                 double resultSI = ((AbstractDoubleScalarAbs<?, ?, ?, ?>) result).getSI();
257                 assertEquals("Result of operation", expectedValue, resultSI, 0.01);
258             }
259         }
260         else
261         {
262             if (doubleType)
263             {
264                 AbstractDoubleScalarRel<?, ?> left = (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(123d,
265                         getSIUnitInstance(getUnitClass(scalarClass), abs));
266                 // System.out.println("constructed left: " + left);
267                 constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
268                 AbstractDoubleScalarRel<?, ?> right = (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(456d,
269                         getSIUnitInstance(getUnitClass(parameterClass), abs));
270                 // System.out.println("constructed right: " + right);
271                 double expectedValue = multiply ? 123d * 456 : 123d / 456;
272 
273                 if (multiply)
274                 {
275                     Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
276                     Object result = multiplyMethod.invoke(left, right);
277                     double resultSI = ((AbstractDoubleScalarRel<?, ?>) result).getSI();
278                     assertEquals("Result of operation", expectedValue, resultSI, 0.01);
279                 }
280                 else
281                 {
282                     Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
283                     Object result = divideMethod.invoke(left, right);
284                     double resultSI = ((AbstractDoubleScalarRel<?, ?>) result).getSI();
285                     assertEquals("Result of operation", expectedValue, resultSI, 0.01);
286                 }
287                 AbstractDoubleScalarRel<?, ?> result =
288                         multiply ? DoubleScalar.multiply(left, right) : DoubleScalar.divide(left, right);
289                 // System.out.println("result is " + result);
290                 String resultCoefficients = result.getDisplayUnit().getQuantity().getSiDimensions().toString();
291                 assertEquals(
292                         "SI coefficients of result of " + left.getClass().getSimpleName() + " x "
293                                 + right.getClass().getSimpleName() + " should match expected SI coefficients",
294                         resultCoefficients, returnSI);
295             }
296             else
297             {
298                 AbstractFloatScalarRel<?, ?> left = (AbstractFloatScalarRel<?, ?>) constructor.newInstance(123f,
299                         getSIUnitInstance(getUnitClass(scalarClass), abs));
300                 // System.out.println("constructed left: " + left);
301                 constructor = parameterClass.getConstructor(double.class, getUnitClass(parameterClass));
302                 AbstractFloatScalarRel<?, ?> right = (AbstractFloatScalarRel<?, ?>) constructor.newInstance(456f,
303                         getSIUnitInstance(getUnitClass(parameterClass), abs));
304                 // System.out.println("constructed right: " + right);
305                 float expectedValue = multiply ? 123f * 456 : 123f / 456;
306 
307                 if (multiply)
308                 {
309                     Method multiplyMethod = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {parameterClass});
310                     Object result = multiplyMethod.invoke(left, right);
311                     double resultSI = ((AbstractFloatScalarRel<?, ?>) result).getSI();
312                     assertEquals("Result of operation", expectedValue, resultSI, 0.01);
313                 }
314                 else
315                 {
316                     Method divideMethod = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {parameterClass});
317                     Object result = divideMethod.invoke(left, right);
318                     float resultSI = ((AbstractFloatScalarRel<?, ?>) result).getSI();
319                     assertEquals("Result of operation", expectedValue, resultSI, 0.01);
320                 }
321                 AbstractFloatScalarRel<?, ?> result =
322                         multiply ? FloatScalar.multiply(left, right) : FloatScalar.divide(left, right);
323                 // System.out.println("result is " + result);
324                 String resultCoefficients = result.getDisplayUnit().getQuantity().getSiDimensions().toString();
325                 assertEquals("SI coefficients of result should match expected SI coefficients", resultCoefficients, returnSI);
326             }
327         }
328     }
329 
330     /**
331      * Obtain the SI coefficient string of a DJUNITS class.
332      * @param clas Class&lt;?&gt;; the DJUNITS class
333      * @return String
334      * @throws IllegalAccessException on class or method resolving error
335      * @throws NoSuchFieldException on class or method resolving error
336      */
337     private String getCoefficients(final Class<?> clas) throws IllegalAccessException, NoSuchFieldException
338     {
339         Field si = clas.getField("SI");
340         Unit<?> u = ((Unit<?>) si.get(clas));
341         String r = u.getQuantity().getSiDimensions().toString();
342         return r;
343     }
344 
345     /**
346      * Obtain the SI coefficient string of a DJUNITS class.
347      * @param clas Class&lt;?&gt;; the DJUNITS class
348      * @param isAbs true when abs
349      * @return String
350      * @throws NoSuchFieldException on class or method resolving error
351      * @throws IllegalAccessException on class or method resolving error
352      */
353     private Unit<?> getSIUnitInstance(final Class<?> clas, final boolean isAbs)
354             throws NoSuchFieldException, IllegalAccessException
355     {
356         Field si = isAbs ? clas.getField("DEFAULT") : clas.getField("SI");
357         return ((Unit<?>) si.get(clas));
358     }
359 
360     /**
361      * Get the unit of a Scalar class by looking at the constructor with two arguments -- the second argument is the unit type.
362      * @param scalarClass the class to find the unit for
363      * @return the unit class for this scalar class
364      */
365     private Class<?> getUnitClass(final Class<?> scalarClass)
366     {
367         Constructor<?>[] constructors = scalarClass.getConstructors();
368         for (Constructor<?> constructor : constructors)
369         {
370             Class<?>[] parTypes = constructor.getParameterTypes();
371             if (parTypes.length == 2 && Unit.class.isAssignableFrom(parTypes[1]))
372             {
373                 return parTypes[1];
374             }
375         }
376         Assert.fail("Could not find constructor with one unit for Scalar class " + scalarClass.getName());
377         return null;
378     }
379 
380     /**
381      * Verify the Absolute-ness or Relative-ness of a DoubleScalar and return the SI value.
382      * @param abs boolean; expected Absolute- or Relative-ness
383      * @param doubleType boolean; if true; double is expected; if false; float is expected
384      * @param o the (DoubleScalar?) object
385      * @return double; the SI value
386      */
387     private double verifyAbsRelPrecisionAndExtractSI(final boolean abs, final boolean doubleType, final Object o)
388     {
389         double result = Double.NaN;
390         if (doubleType)
391         {
392             if (!(o instanceof DoubleScalarInterface))
393             {
394                 fail("object is not a DoubleScalar");
395             }
396             result = ((AbstractDoubleScalar<?, ?>) o).getSI();
397         }
398         else
399         {
400             if (!(o instanceof FloatScalarInterface))
401             {
402                 fail("object is not a FloatScalar");
403             }
404             result = ((AbstractFloatScalar<?, ?>) o).getSI();
405         }
406         if (o instanceof Absolute)
407         {
408             if (!abs)
409             {
410                 fail("Result should have been Absolute");
411             }
412         }
413         else if (o instanceof Relative)
414         {
415             if (abs)
416             {
417                 fail("Result should have been Relative");
418             }
419         }
420         else
421         {
422             fail("Result is neither Absolute, nor Relative");
423         }
424         return result;
425     }
426 
427     /**
428      * @param scalarClass the class to test
429      * @param abs boolean; if true; test Absolute class; if false; test the Relative class
430      * @param doubleType boolean; if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
431      * @throws NoSuchMethodException on class or method resolving error
432      * @throws InstantiationException on class or method resolving error
433      * @throws IllegalAccessException on class or method resolving error
434      * @throws InvocationTargetException on class or method resolving error
435      * @throws NoSuchFieldException on class or method resolving error
436      * @throws ClassNotFoundException on class or method resolving error
437      */
438     @SuppressWarnings({"rawtypes", "unchecked"})
439     private void testUnaryMethods(final Class<?> scalarClass, final boolean abs, final boolean doubleType)
440             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
441             NoSuchFieldException, ClassNotFoundException
442     {
443         double value = 1.23456;
444         Constructor<?> constructor =
445                 scalarClass.getConstructor(doubleType ? double.class : float.class, getUnitClass(scalarClass));
446         Object left;
447         if (doubleType)
448         {
449             left = abs
450                     ? (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(value,
451                             getSIUnitInstance(getUnitClass(scalarClass), abs))
452                     : (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(value,
453                             getSIUnitInstance(getUnitClass(scalarClass), abs));
454             // Find the constructor that takes an object of the current class as the single argument
455             Constructor<?>[] constructors = scalarClass.getConstructors();
456             for (Constructor<?> c : constructors)
457             {
458                 Class<?>[] parTypes = c.getParameterTypes();
459                 if (parTypes.length == 1)
460                 {
461                     AbstractDoubleScalar<?, ?> newInstance = (AbstractDoubleScalar<?, ?>) c.newInstance(left);
462                     assertEquals("Result of constructor should be equal to original", value,
463                             verifyAbsRelPrecisionAndExtractSI(abs, doubleType, newInstance), 0.01);
464                 }
465             }
466         }
467         else
468         {
469             left = abs
470                     ? (AbstractFloatScalarAbs<?, ?, ?, ?>) constructor.newInstance((float) value,
471                             getSIUnitInstance(getUnitClass(scalarClass), abs))
472                     : (AbstractFloatScalarRel<?, ?>) constructor.newInstance((float) value,
473                             getSIUnitInstance(getUnitClass(scalarClass), abs));
474             // Find the constructor that takes an object of the current class as the single argument
475             Constructor<?>[] constructors = scalarClass.getConstructors();
476             for (Constructor<?> c : constructors)
477             {
478                 Class<?>[] parTypes = c.getParameterTypes();
479                 if (parTypes.length == 1)
480                 {
481                     // System.out.println("parType is " + parTypes[0]);
482                     AbstractFloatScalar<?, ?> newInstance = (AbstractFloatScalar<?, ?>) c.newInstance(left);
483                     assertEquals("Result of constructor should be equal to original", value,
484                             verifyAbsRelPrecisionAndExtractSI(abs, doubleType, newInstance), 0.01);
485                 }
486             }
487         }
488         Object result;
489 
490         Method ceil = ClassUtil.resolveMethod(scalarClass, "ceil", new Class[] {});
491         result = ceil.invoke(left);
492         assertEquals("Result of operation", Math.ceil(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01);
493 
494         Method floor = ClassUtil.resolveMethod(scalarClass, "floor", new Class[] {});
495         result = floor.invoke(left);
496         assertEquals("Result of operation", Math.floor(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
497                 0.01);
498 
499         Method rint = ClassUtil.resolveMethod(scalarClass, "rint", new Class[] {});
500         result = rint.invoke(left);
501         assertEquals("Result of operation", Math.rint(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01);
502 
503         if (!abs)
504         {
505             Method methodAbs = ClassUtil.resolveMethod(scalarClass, "abs", new Class[] {});
506             result = methodAbs.invoke(left);
507             assertEquals("Result of operation", Math.abs(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
508                     0.01);
509         }
510 
511         if (scalarClass.getName().contains("Dimensionless"))
512         {
513             Method asin = ClassUtil.resolveMethod(scalarClass, "asin", new Class[] {});
514             result = asin.invoke(left);
515             assertEquals("Result of operation", Math.asin(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
516                     0.01);
517 
518             Method acos = ClassUtil.resolveMethod(scalarClass, "acos", new Class[] {});
519             result = acos.invoke(left);
520             assertEquals("Result of operation", Math.acos(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
521                     0.01);
522 
523             Method atan = ClassUtil.resolveMethod(scalarClass, "atan", new Class[] {});
524             result = atan.invoke(left);
525             assertEquals("Result of operation", Math.atan(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
526                     0.01);
527 
528             Method cbrt = ClassUtil.resolveMethod(scalarClass, "cbrt", new Class[] {});
529             result = cbrt.invoke(left);
530             assertEquals("Result of operation", Math.cbrt(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
531                     0.01);
532 
533             Method cos = ClassUtil.resolveMethod(scalarClass, "cos", new Class[] {});
534             result = cos.invoke(left);
535             assertEquals("Result of operation", Math.cos(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
536                     0.01);
537 
538             Method cosh = ClassUtil.resolveMethod(scalarClass, "cosh", new Class[] {});
539             result = cosh.invoke(left);
540             assertEquals("Result of operation", Math.cosh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
541                     0.01);
542 
543             Method exp = ClassUtil.resolveMethod(scalarClass, "exp", new Class[] {});
544             result = exp.invoke(left);
545             assertEquals("Result of operation", Math.exp(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
546                     0.01);
547 
548             Method expm1 = ClassUtil.resolveMethod(scalarClass, "expm1", new Class[] {});
549             result = expm1.invoke(left);
550             assertEquals("Result of operation", Math.expm1(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
551                     0.01);
552 
553             Method log = ClassUtil.resolveMethod(scalarClass, "log", new Class[] {});
554             result = log.invoke(left);
555             assertEquals("Result of operation", Math.log(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
556                     0.01);
557 
558             Method log10 = ClassUtil.resolveMethod(scalarClass, "log10", new Class[] {});
559             result = log10.invoke(left);
560             assertEquals("Result of operation", Math.log10(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
561                     0.01);
562 
563             Method log1p = ClassUtil.resolveMethod(scalarClass, "log1p", new Class[] {});
564             result = log1p.invoke(left);
565             assertEquals("Result of operation", Math.log1p(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
566                     0.01);
567 
568             Method signum = ClassUtil.resolveMethod(scalarClass, "signum", new Class[] {});
569             result = signum.invoke(left);
570             assertEquals("Result of operation", Math.signum(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
571                     0.01);
572 
573             Method sin = ClassUtil.resolveMethod(scalarClass, "sin", new Class[] {});
574             result = sin.invoke(left);
575             assertEquals("Result of operation", Math.sin(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
576                     0.01);
577 
578             Method sinh = ClassUtil.resolveMethod(scalarClass, "sinh", new Class[] {});
579             result = sinh.invoke(left);
580             assertEquals("Result of operation", Math.sinh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
581                     0.01);
582 
583             Method sqrt = ClassUtil.resolveMethod(scalarClass, "sqrt", new Class[] {});
584             result = sqrt.invoke(left);
585             assertEquals("Result of operation", Math.sqrt(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
586                     0.01);
587 
588             Method tan = ClassUtil.resolveMethod(scalarClass, "tan", new Class[] {});
589             result = tan.invoke(left);
590             assertEquals("Result of operation", Math.tan(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
591                     0.01);
592 
593             Method tanh = ClassUtil.resolveMethod(scalarClass, "tanh", new Class[] {});
594             result = tanh.invoke(left);
595             assertEquals("Result of operation", Math.tanh(value), verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
596                     0.01);
597 
598             Method inv = ClassUtil.resolveMethod(scalarClass, "inv", new Class[] {});
599             result = inv.invoke(left);
600             assertEquals("Result of operation", 1 / value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01);
601 
602             Method pow = ClassUtil.resolveMethod(scalarClass, "pow", new Class[] {double.class});
603             result = pow.invoke(left, Math.PI);
604             assertEquals("Result of operation", Math.pow(value, Math.PI),
605                     verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result), 0.01);
606         }
607 
608         Object compatibleRight = null;
609         // TODO: Probably we exclude too much here for the tests...
610         if (!scalarClass.getName().contains("Dimensionless") && !scalarClass.getName().contains("AbsoluteTemperature")
611                 && !scalarClass.getName().contains("Position") && !scalarClass.getName().contains("Time")
612                 && !scalarClass.getName().contains("Direction"))
613         {
614             // Construct a new unit to test mixed unit plus and minus
615             Class<?> unitClass = getUnitClass(scalarClass);
616             UnitSystem unitSystem = UnitSystem.SI_DERIVED;
617             // Call the getUnit method of left
618             // Method getUnitMethod = ClassUtil.resolveMethod(scalarClass, "getDisplayUnit");
619             Constructor<?> unitConstructor = unitClass.getConstructor(); // empty constructor -- provide Builder
620             Unit newUnit = (Unit) unitConstructor.newInstance();
621             Method buildMethod = ClassUtil.resolveMethod(Unit.class, "build", Unit.Builder.class);
622             Unit.Builder<?> builder = new Unit.Builder<>();
623             builder.setId("7abbr");
624             builder.setName("7fullName");
625             builder.setUnitSystem(unitSystem);
626             builder.setScale(new LinearScale(7));
627             builder.setQuantity((Quantity) getSIUnitInstance(unitClass, false).getQuantity());
628             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
629             buildMethod.setAccessible(true);
630             buildMethod.invoke(newUnit, builder);
631 
632             // System.out.println("new unit prints like " + newUnit);
633             if (doubleType)
634             {
635                 compatibleRight = abs ? (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(value, newUnit)
636                         : (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(value, newUnit);
637             }
638             else
639             {
640                 compatibleRight = abs ? (AbstractFloatScalarAbs<?, ?, ?, ?>) constructor.newInstance((float) value, newUnit)
641                         : (AbstractFloatScalarRel<?, ?>) constructor.newInstance((float) value, newUnit);
642             }
643             // System.out.println("compatibleRight prints like \"" + compatibleRight + "\"");
644             newUnit.getQuantity().unregister(newUnit);
645         }
646         if (!abs)
647         {
648             Method times = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {double.class});
649             result = doubleType ? times.invoke(left, Math.PI) : times.invoke(left, (float) Math.PI);
650             assertEquals("Result of operation", Math.PI * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
651                     0.01);
652             if (!doubleType)
653             {
654                 times = ClassUtil.resolveMethod(scalarClass, "times", new Class[] {float.class});
655                 result = doubleType ? times.invoke(left, Math.PI) : times.invoke(left, (float) Math.PI);
656                 assertEquals("Result of operation", Math.PI * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
657                         0.01);
658             }
659 
660             Method divide = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {double.class});
661             result = doubleType ? divide.invoke(left, Math.PI) : divide.invoke(left, (float) Math.PI);
662             assertEquals("Result of operation", value / Math.PI, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
663                     0.01);
664             if (!doubleType)
665             {
666                 divide = ClassUtil.resolveMethod(scalarClass, "divide", new Class[] {float.class});
667                 result = doubleType ? divide.invoke(left, Math.PI) : divide.invoke(left, (float) Math.PI);
668                 assertEquals("Result of operation", value / Math.PI, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
669                         0.01);
670             }
671 
672             Method plus = ClassUtil.resolveMethod(scalarClass, "plus", new Class[] {scalarClass});
673             result = plus.invoke(left, left);
674             assertEquals("Result of operation", value + value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
675                     0.01);
676 
677             if (null != compatibleRight)
678             {
679                 result = plus.invoke(left, compatibleRight);
680                 assertEquals("Result of mixed operation", 8 * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
681                         0.01);
682                 // Swap the operands
683                 // System.out.println("finding plus method for " + compatibleRight.getClass().getName() + " left type is "
684                 // + left.getClass().getName());
685                 plus = ClassUtil.resolveMethod(scalarClass, "plus", new Class[] {compatibleRight.getClass().getSuperclass()});
686                 result = plus.invoke(compatibleRight, left);
687                 assertEquals("Result of mixed operation", 8 * value, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
688                         0.01);
689                 if (scalarClass.getName().contains("$Rel"))
690                 {
691                     // Make an Absolute for one operand
692                     String absScalarClassName = scalarClass.getName().replace("$Rel", "$Abs");
693                     Class<?> absScalarClass = Class.forName(absScalarClassName);
694                     Constructor<?> absScalarConstructor = absScalarClass.getConstructor(doubleType ? double.class : float.class,
695                             getUnitClass(absScalarClass));
696                     Object absOperand = null;
697                     // System.out.println("unit is " + getUnitClass(absScalarClass));
698                     if (doubleType)
699                     {
700                         absOperand =
701                                 absScalarConstructor.newInstance(value, getSIUnitInstance(getUnitClass(absScalarClass), true));
702                     }
703                     else
704                     {
705                         absOperand = absScalarConstructor.newInstance((float) value,
706                                 getSIUnitInstance(getUnitClass(absScalarClass), true));
707                     }
708                     // abs plus rel yields abs
709                     plus = ClassUtil.resolveMethod(absScalarClass, "plus", scalarClass);
710                     result = plus.invoke(absOperand, left);
711                     assertEquals("Result of mixed abs + rel", 2 * value,
712                             verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01);
713                     Method toAbs = compatibleRight.getClass().getMethod("toAbs");
714                     Object absCompatible = toAbs.invoke(compatibleRight);
715                     result = plus.invoke(absCompatible, left);
716                     assertEquals("Result of mixed compatible abs + rel", 8 * value,
717                             verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01);
718                     // rel plus abs yields abs
719                     plus = ClassUtil.resolveMethod(scalarClass, "plus", absScalarClass);
720                     result = plus.invoke(left, absOperand);
721                     assertEquals("Result of mixed rel + abs", 2 * value,
722                             verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01);
723                     result = plus.invoke(left, absCompatible);
724                     assertEquals("Result of mixed rel + compatible abs", 8 * value,
725                             verifyAbsRelPrecisionAndExtractSI(true, doubleType, result), 0.01);
726                 }
727             }
728         }
729 
730         Method minus = ClassUtil.resolveMethod(scalarClass, "minus", new Class[] {scalarClass});
731         result = minus.invoke(left, left);
732         assertEquals("Result of minus", 0, verifyAbsRelPrecisionAndExtractSI(false, doubleType, result), 0.01);
733         if (null != compatibleRight)
734         {
735             result = minus.invoke(left, compatibleRight);
736             assertEquals("Result of minus with compatible arg for " + left + " and " + compatibleRight, -6 * value,
737                     verifyAbsRelPrecisionAndExtractSI(false, doubleType, result), 0.01);
738         }
739         if (scalarClass.getName().contains("$Rel") || scalarClass.getName().contains("$Abs"))
740         {
741             // Make an Absolute for one operand
742             String absScalarClassName = scalarClass.getName().replace("$Rel", "$Abs");
743             Class<?> absScalarClass = Class.forName(absScalarClassName);
744             Constructor<?> absScalarConstructor =
745                     absScalarClass.getConstructor(doubleType ? double.class : float.class, getUnitClass(absScalarClass));
746             Object absOperand = null;
747             // System.out.println("unit is " + getUnitClass(absScalarClass));
748             if (doubleType)
749             {
750                 absOperand = absScalarConstructor.newInstance(value, getSIUnitInstance(getUnitClass(absScalarClass), true));
751             }
752             else
753             {
754                 absOperand =
755                         absScalarConstructor.newInstance((float) value, getSIUnitInstance(getUnitClass(absScalarClass), true));
756             }
757             minus = ClassUtil.resolveMethod(absScalarClass, "minus", scalarClass);
758             result = minus.invoke(absOperand, left);
759             assertEquals("Result of abs or rel minus rel", 0, verifyAbsRelPrecisionAndExtractSI(!abs, doubleType, result),
760                     0.01);
761             if (null != compatibleRight && scalarClass.getName().contains("$Rel"))
762             {
763                 Method toAbs = compatibleRight.getClass().getMethod("toAbs");
764                 Object absCompatible = toAbs.invoke(compatibleRight);
765                 result = minus.invoke(absCompatible, left);
766                 assertEquals("Result of compatible abs or rel minus rel", 6 * value,
767                         verifyAbsRelPrecisionAndExtractSI(!abs, doubleType, result), 0.01);
768             }
769 
770         }
771     }
772 
773     /**
774      * Test the various static methods.
775      * @param scalarClass Class&lt;?&gt;; the class to test
776      * @param abs boolean; if true; scalarClass is Absolute; if false; scalarClass is Relative
777      * @param doubleType boolean; if true; perform tests on DoubleScalar; if false; perform tests on FloatScalar
778      * @throws NoSuchMethodException on class or method resolving error
779      * @throws NoSuchFieldException on class or method resolving error
780      * @throws InvocationTargetException on class or method resolving error
781      * @throws IllegalAccessException on class or method resolving error
782      * @throws InstantiationException on class or method resolving error
783      */
784     private void testStaticMethods(final Class<?> scalarClass, final boolean abs, final boolean doubleType)
785             throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
786             NoSuchFieldException
787     {
788         Constructor<?> constructor = scalarClass.getConstructor(double.class, getUnitClass(scalarClass));
789         if (doubleType)
790         {
791             double zeroValue = 1.23456;
792             AbstractDoubleScalar<?,
793                     ?> zero = abs
794                             ? (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(zeroValue,
795                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
796                             : (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(zeroValue,
797                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
798             double oneValue = 3.45678;
799             AbstractDoubleScalar<?,
800                     ?> one = abs
801                             ? (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(oneValue,
802                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
803                             : (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(oneValue,
804                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
805             for (double ratio : new double[] {-5, -1, 0, 0.3, 1, 2, 10})
806             {
807                 double expectedResult = (1.0 - ratio) * zeroValue + ratio * oneValue;
808                 Method interpolate =
809                         ClassUtil.resolveMethod(scalarClass, "interpolate", scalarClass, scalarClass, double.class);
810                 AbstractDoubleScalar<?, ?> result;
811                 result = (AbstractDoubleScalar<?, ?>) interpolate.invoke(null, zero, one, ratio);
812                 assertEquals("Result of operation", expectedResult, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
813                         0.01);
814             }
815             double biggestValue = 345.678;
816             AbstractDoubleScalar<?,
817                     ?> biggest = abs
818                             ? (AbstractDoubleScalarAbs<?, ?, ?, ?>) constructor.newInstance(biggestValue,
819                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
820                             : (AbstractDoubleScalarRel<?, ?>) constructor.newInstance(biggestValue,
821                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
822             Method max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass);
823             AbstractDoubleScalar<?, ?> result = (AbstractDoubleScalar<?, ?>) max.invoke(null, zero, one);
824             assertEquals("max returns object with maximum value", one, result);
825             result = (AbstractDoubleScalar<?, ?>) max.invoke(null, one, zero);
826             assertEquals("max returns object with maximum value", one, result);
827             // https://stackoverflow.com/questions/1679421/how-to-get-the-array-class-for-a-given-class-in-java
828             Class<?> emptyClassArrayClass = java.lang.reflect.Array.newInstance(scalarClass, 0).getClass();
829             max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass, emptyClassArrayClass);
830             AbstractDoubleScalar<?, ?>[] additionalArguments =
831                     (AbstractDoubleScalar<?, ?>[]) java.lang.reflect.Array.newInstance(scalarClass, 1);
832             additionalArguments[0] = biggest;
833             result = (AbstractDoubleScalar<?, ?>) max.invoke(null, zero, one, additionalArguments);
834             assertEquals("max return object with maximum value", biggest, result);
835             result = (AbstractDoubleScalar<?, ?>) max.invoke(null, one, zero, additionalArguments);
836             assertEquals("max return object with maximum value", biggest, result);
837             additionalArguments[0] = zero;
838             result = (AbstractDoubleScalar<?, ?>) max.invoke(null, biggest, zero, additionalArguments);
839             assertEquals("max return object with maximum value", biggest, result);
840 
841             Method min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass);
842             result = (AbstractDoubleScalar<?, ?>) min.invoke(null, zero, one);
843             assertEquals("min returns object with maximum value", zero, result);
844             result = (AbstractDoubleScalar<?, ?>) min.invoke(null, one, zero);
845             assertEquals("min returns object with maximum value", zero, result);
846             min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass, emptyClassArrayClass);
847             result = (AbstractDoubleScalar<?, ?>) min.invoke(null, one, biggest, additionalArguments);
848             assertEquals("min return object with minimum value", zero, result);
849             result = (AbstractDoubleScalar<?, ?>) min.invoke(null, biggest, one, additionalArguments);
850             assertEquals("min return object with minimum value", zero, result);
851             additionalArguments[0] = biggest;
852             result = (AbstractDoubleScalar<?, ?>) min.invoke(null, zero, one, additionalArguments);
853             assertEquals("min return object with minimum value", zero, result);
854 
855             Method valueOf = ClassUtil.resolveMethod(scalarClass, "valueOf", String.class);
856             String string = zero.toString();
857             result = (AbstractDoubleScalar<?, ?>) valueOf.invoke(null, string);
858             assertEquals("valueOf toString returns a decent approximation of the input", zeroValue, result.getSI(), 0.001);
859             try
860             {
861                 valueOf.invoke(null, (String) null);
862                 fail("Null string in valueOf should have thrown an IllegalArgumentException (which may have been converted "
863                         + "into an InvocationTargetException)");
864             }
865             catch (IllegalArgumentException | InvocationTargetException iae)
866             {
867                 // Ignore expected exception
868             }
869 
870             try
871             {
872                 valueOf.invoke(null, "");
873                 fail("Empty string in valueOf should have thrown an IllegalArgumentException");
874             }
875             catch (IllegalArgumentException | InvocationTargetException iae)
876             {
877                 // Ignore expected exception
878             }
879 
880             try
881             {
882                 valueOf.invoke(null, "NONSENSEVALUE");
883                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
884             }
885             catch (IllegalArgumentException | InvocationTargetException iae)
886             {
887                 // Ignore expected exception
888             }
889 
890             try
891             {
892                 valueOf.invoke(null, "1.0 xyzuwv");
893                 fail("Nonsense unit string in valueOf argument should have thrown an IllegalArgumentException");
894             }
895             catch (IllegalArgumentException | InvocationTargetException iae)
896             {
897                 // Ignore expected exception
898             }
899 
900             Method instantiateSI = ClassUtil.resolveMethod(scalarClass, "instantiateSI", double.class);
901             result = (AbstractDoubleScalar<?, ?>) instantiateSI.invoke(null, zeroValue);
902             assertEquals("SI value was correctly set", zeroValue, result.getSI(), 0.0001);
903         }
904         else
905         {
906             float zeroValue = 1.23456f;
907             AbstractFloatScalar<?,
908                     ?> zero = abs
909                             ? (AbstractFloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(zeroValue,
910                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
911                             : (AbstractFloatScalarRel<?, ?>) constructor.newInstance(zeroValue,
912                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
913             float oneValue = 3.45678f;
914             AbstractFloatScalar<?,
915                     ?> one = abs
916                             ? (AbstractFloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(oneValue,
917                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
918                             : (AbstractFloatScalarRel<?, ?>) constructor.newInstance(oneValue,
919                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
920             for (float ratio : new float[] {-5, -1, 0, 0.3f, 1, 2, 10})
921             {
922                 float expectedResult = (1.0f - ratio) * zeroValue + ratio * oneValue;
923                 Method interpolate = ClassUtil.resolveMethod(scalarClass, "interpolate", scalarClass, scalarClass, float.class);
924                 AbstractFloatScalar<?, ?> result;
925                 result = (AbstractFloatScalar<?, ?>) interpolate.invoke(null, zero, one, ratio);
926                 assertEquals("Result of operation", expectedResult, verifyAbsRelPrecisionAndExtractSI(abs, doubleType, result),
927                         0.01);
928             }
929             float biggestValue = 345.678f;
930             AbstractFloatScalar<?,
931                     ?> biggest = abs
932                             ? (AbstractFloatScalarAbs<?, ?, ?, ?>) constructor.newInstance(biggestValue,
933                                     getSIUnitInstance(getUnitClass(scalarClass), abs))
934                             : (AbstractFloatScalarRel<?, ?>) constructor.newInstance(biggestValue,
935                                     getSIUnitInstance(getUnitClass(scalarClass), abs));
936             Method max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass);
937             AbstractFloatScalar<?, ?> result = (AbstractFloatScalar<?, ?>) max.invoke(null, zero, one);
938             assertEquals("max return object with maximum value", one, result);
939             result = (AbstractFloatScalar<?, ?>) max.invoke(null, one, zero);
940             assertEquals("max return object with maximum value", one, result);
941             // https://stackoverflow.com/questions/1679421/how-to-get-the-array-class-for-a-given-class-in-java
942             Class<?> emptyClassArrayClass = java.lang.reflect.Array.newInstance(scalarClass, 0).getClass();
943             max = ClassUtil.resolveMethod(scalarClass, "max", scalarClass, scalarClass, emptyClassArrayClass);
944             AbstractFloatScalar<?, ?>[] additionalArguments =
945                     (AbstractFloatScalar<?, ?>[]) java.lang.reflect.Array.newInstance(scalarClass, 1);
946             additionalArguments[0] = biggest;
947             result = (AbstractFloatScalar<?, ?>) max.invoke(null, zero, one, additionalArguments);
948             assertEquals("max return object with maximum value", biggest, result);
949             result = (AbstractFloatScalar<?, ?>) max.invoke(null, one, zero, additionalArguments);
950             assertEquals("max return object with maximum value", biggest, result);
951             additionalArguments[0] = zero;
952             result = (AbstractFloatScalar<?, ?>) max.invoke(null, biggest, zero, additionalArguments);
953             assertEquals("max return object with maximum value", biggest, result);
954 
955             Method min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass);
956             result = (AbstractFloatScalar<?, ?>) min.invoke(null, zero, one);
957             assertEquals("min returns object with maximum value", zero, result);
958             result = (AbstractFloatScalar<?, ?>) min.invoke(null, one, zero);
959             assertEquals("min returns object with maximum value", zero, result);
960             min = ClassUtil.resolveMethod(scalarClass, "min", scalarClass, scalarClass, emptyClassArrayClass);
961             result = (AbstractFloatScalar<?, ?>) min.invoke(null, one, biggest, additionalArguments);
962             assertEquals("min return object with minimum value", zero, result);
963             result = (AbstractFloatScalar<?, ?>) min.invoke(null, biggest, one, additionalArguments);
964             assertEquals("min return object with minimum value", zero, result);
965             additionalArguments[0] = biggest;
966             result = (AbstractFloatScalar<?, ?>) min.invoke(null, zero, one, additionalArguments);
967             assertEquals("min return object with minimum value", zero, result);
968 
969             Method valueOf = ClassUtil.resolveMethod(scalarClass, "valueOf", String.class);
970             String string = zero.toString();
971             result = (AbstractFloatScalar<?, ?>) valueOf.invoke(null, string);
972             assertEquals("valueOf toString returns a decent approximation of the input", zeroValue, result.getSI(), 0.001);
973             try
974             {
975                 valueOf.invoke(null, (String) null);
976                 fail("Null string in valueOf should have thrown an IllegalArgumentException (which may have been converted "
977                         + "into an InvocationTargetException)");
978             }
979             catch (IllegalArgumentException | InvocationTargetException iae)
980             {
981                 // Ignore expected exception
982             }
983 
984             try
985             {
986                 valueOf.invoke(null, "");
987                 fail("Empty string in valueOf should have thrown an IllegalArgumentException");
988             }
989             catch (IllegalArgumentException | InvocationTargetException iae)
990             {
991                 // Ignore expected exception
992             }
993 
994             try
995             {
996                 valueOf.invoke(null, "NONSENSEVALUE");
997                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
998             }
999             catch (IllegalArgumentException | InvocationTargetException iae)
1000             {
1001                 // Ignore expected exception
1002             }
1003 
1004             try
1005             {
1006                 valueOf.invoke(null, "1.0 xyzuwv");
1007                 fail("Nonsense string in valueOf should have thrown an IllegalArgumentException");
1008             }
1009             catch (IllegalArgumentException | InvocationTargetException iae)
1010             {
1011                 // Ignore expected exception
1012             }
1013 
1014             Method instantiateSI = ClassUtil.resolveMethod(scalarClass, "instantiateSI", float.class);
1015             result = (AbstractFloatScalar<?, ?>) instantiateSI.invoke(null, zeroValue);
1016             assertEquals("SI value was correctly set", zeroValue, result.getSI(), 0.0001);
1017         }
1018     }
1019 
1020 }