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