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