package nxm.sys.test.junit.lib;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import nxm.sys.lib.Midas;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;
import nxm.sys.lib.Util;
import nxm.sys.test.junit.NMTestCase;

/** This will test the Util class.

    @author  J. Czechowski IV
 */
public final class UtilTest extends NMTestCase {
  /** public constructor to allow running this test case directly with JUnit. */
  public UtilTest (String methodName) { this(methodName, null); }

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
      @param midas      The midas context to use when running the tests.
   */
  public UtilTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  //////////////////////////////////////////////////////////////////////////////

  public void testCombineArrays() {
    final int[]     intArrayA   = { 1, 2, 3 };
    final int[]     intArrayB   = { 4, 5, 6, 7, 8 };
    final int[]     intArrayAB  = { 1, 2, 3, 4, 5, 6, 7, 8 };
    final int[]     intArrayAB2 = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
    final int[]     intArrayBA  = { 4, 5, 6, 7, 8, 1, 2, 3 };
    final String[]  strArrayA   = { "A", "B", "C" };
    final String[]  strArrayB   = { "1", "2", "3" };
    final String[]  strArrayAB  = { strArrayA[0], strArrayA[1], strArrayA[2],
                                    strArrayB[0], strArrayB[1], strArrayB[2] };
    final String[]  strArrayAB2 = { strArrayA[0], strArrayA[1], strArrayA[2],
                                    strArrayB[0], strArrayB[1], strArrayB[2],
                                    null,         null,         null         };
    final Integer[] numArrayA   = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
    final Object[]  objArray    = { strArrayA[0], strArrayA[1], strArrayA[2],
                                    strArrayB[0], strArrayB[1], strArrayB[2],
                                    numArrayA[0], numArrayA[1], numArrayA[2] };


    int[] myIntAB0 = (int[])Util.combineArrays(intArrayA, intArrayB);
    int[] myIntAB1 = (int[])Util.combineArrays(null, intArrayA, intArrayB);
    int[] myIntAB2 = new int[9];
    int[] myIntAB3 = new int[8];
    int[] myIntAB4 = (int[])Util.combineArrays(myIntAB2, intArrayA, intArrayB);
    int[] myIntAB5 = (int[])Util.combineArrays(myIntAB3, intArrayA, intArrayB);

    assertTrue("combineArrays(intArrayA,intArrayB)",           Arrays.equals(myIntAB0,intArrayAB));
    assertTrue("combineArrays(null,intArrayA,intArrayB)",      Arrays.equals(myIntAB1,intArrayAB));
    assertTrue("combineArrays(new int[9],intArrayA,intArrayB)",Arrays.equals(myIntAB2,intArrayAB2));
    assertTrue("combineArrays(new int[8],intArrayA,intArrayB)",Arrays.equals(myIntAB3,intArrayAB));
    assertSame("myIntAB2 == myIntAB4", myIntAB2, myIntAB4);
    assertSame("myIntAB3 == myIntAB5", myIntAB3, myIntAB5);

    int[] myIntBA0 = (int[])Util.combineArrays(intArrayB, intArrayA);
    int[] myIntBA1 = (int[])Util.combineArrays(null, intArrayB, intArrayA);
    assertTrue("combineArrays(intArrayB,intArrayA)",            Arrays.equals(myIntBA0,intArrayBA));
    assertTrue("combineArrays(null,intArrayB,intArrayA)",       Arrays.equals(myIntBA1,intArrayBA));


    Object[] myStrAB0 = (Object[])Util.combineArrays(strArrayA,strArrayB);
    Object[] myStrAB1 = (Object[])Util.combineArrays(null,strArrayA,strArrayB);
    Object[] myStrAB2 = new String[9];
    Object[] myStrAB3 = new Object[6];
    Object[] myStrAB4 = (Object[])Util.combineArrays(myStrAB2,strArrayA,strArrayB);
    Object[] myStrAB5 = (Object[])Util.combineArrays(myStrAB3,strArrayA,strArrayB);

    assertTrue("combineArrays(strArrayA,strArrayB)",           Arrays.equals(myStrAB0,strArrayAB));
    assertTrue("combineArrays(null,strArrayA,strArrayB)",      Arrays.equals(myStrAB1,strArrayAB));
    assertTrue("combineArrays(new Obj[9],strArrayA,strArrayB)",Arrays.equals(myStrAB2,strArrayAB2));
    assertTrue("combineArrays(new Obj[6],strArrayA,strArrayB)",Arrays.equals(myStrAB3,strArrayAB));
    assertSame("myStrAB2 == myStrAB4", myStrAB2, myStrAB4);
    assertSame("myStrAB3 == myStrAB5", myStrAB3, myStrAB5);
    assertEquals("myStrAB0 instanceof String[]", String[].class, myStrAB0.getClass());
    assertEquals("myStrAB1 instanceof String[]", String[].class, myStrAB1.getClass());
    assertEquals("myStrAB2 instanceof String[]", String[].class, myStrAB2.getClass());
    assertEquals("myStrAB3 instanceof Object[]", Object[].class, myStrAB3.getClass());


    Object[] myObjAB0 = (Object[])Util.combineArrays(Util.combineArrays(strArrayA,strArrayB),
                                                     numArrayA);
    Object[] myObjAB1 = (Object[])Util.combineArrays(null, strArrayA,
                                                     Util.combineArrays(null,strArrayB,numArrayA));

    assertTrue("combineArrays(combineArrays(strA,strB0,num))",
               Arrays.equals(myObjAB0,objArray));
    assertTrue("combineArrays(null,strA,combineArrays(null,strB,numA))",
               Arrays.equals(myObjAB1,objArray));
    assertEquals("myObjAB0 instanceof Object[]", Object[].class, myObjAB0.getClass());
    assertEquals("myObjAB1 instanceof Object[]", Object[].class, myObjAB1.getClass());
  }

  public void testCombineArraysAs() {
    final String[]  strArrayA   = { "A", "B", "C" };
    final String[]  strArrayB   = { "1", "2", "3" };
    final Integer[] numArrayA   = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
    final Integer[] numArrayB   = { Integer.valueOf(4), Integer.valueOf(5), Integer.valueOf(6) };
    final String[]  strArrayAB  = { strArrayA[0], strArrayA[1], strArrayA[2],
                                    strArrayB[0], strArrayB[1], strArrayB[2] };
    final Object[]  numArrayAB  = { numArrayA[0], numArrayA[1], numArrayA[2],
                                    numArrayB[0], numArrayB[1], numArrayB[2] };
    final Object[]  objArray    = { strArrayA[0], strArrayA[1], strArrayA[2],
                                    numArrayA[0], numArrayA[1], numArrayA[2] };

    Object[] myObj0 = (Object[])Util.combineArraysAs(String.class,strArrayA,strArrayB);
    Object[] myObj1 = (Object[])Util.combineArraysAs(Object.class,strArrayA,strArrayB);
    Object[] myObj2 = (Object[])Util.combineArraysAs(Object.class,strArrayA,numArrayA);
    Object[] myObj3 = (Object[])Util.combineArraysAs(Integer.class,numArrayA,numArrayB);
    Object[] myObj4 = (Object[])Util.combineArraysAs(Number.class,numArrayA,numArrayB);
    Object[] myObj5 = (Object[])Util.combineArraysAs(Object.class,numArrayA,numArrayB);


    assertTrue("combineArraysAs(String,strArrayA,strArrayB)",  Arrays.equals(myObj0,strArrayAB));
    assertTrue("combineArraysAs(Object,strArrayA,strArrayB)",  Arrays.equals(myObj1,strArrayAB));
    assertTrue("combineArraysAs(Object,strArrayA,numArrayA)",  Arrays.equals(myObj2,objArray));
    assertTrue("combineArraysAs(Object,numArrayA,numArrayB)",  Arrays.equals(myObj3,numArrayAB));
    assertTrue("combineArraysAs(Object,numArrayA,numArrayB)",  Arrays.equals(myObj4,numArrayAB));
    assertTrue("combineArraysAs(Object,numArrayA,numArrayB)",  Arrays.equals(myObj5,numArrayAB));
    assertEquals("myObj0 instanceof String[]",  String[].class,  myObj0.getClass());
    assertEquals("myObj1 instanceof Object[]",  Object[].class,  myObj1.getClass());
    assertEquals("myObj2 instanceof Object[]",  Object[].class,  myObj2.getClass());
    assertEquals("myObj3 instanceof Integer[]", Integer[].class, myObj3.getClass());
    assertEquals("myObj4 instanceof Number[]",  Number[].class,  myObj4.getClass());
    assertEquals("myObj5 instanceof Object[]",  Object[].class,  myObj5.getClass());
  }

  public void testReverseArray () {
    String[] evenArray0    = { "1", "2", "3", "4", "5", "6" };
    String[] evenArray1    = new String[evenArray0.length];
    String[] expEvenArray0 = { "6", "5", "4", "3", "2", "1" };
    String[] expEvenArray1 = { "1", "6", "5", "4", "3", "2" };
    String[] oddArray0     = { "1", "2", "3", "4", "5", "6", "7" };
    String[] oddArray1     = new String[oddArray0.length];
    String[] expOddArray0  = { "7", "6", "5", "4", "3", "2", "1" };
    String[] expOddArray1  = { "1", "7", "6", "5", "4", "3", "2" };
    String[] reversedArray;

    System.arraycopy(evenArray0, 0, evenArray1, 0, evenArray0.length);
    reversedArray = Util.reverseArray(evenArray0, 0);
    assertTrue("Reverse array of even length starting at index 0", Arrays.equals(evenArray0, expEvenArray0));
    assertTrue("Reverse array of even length starting at index 0 (returned array)", (evenArray0==reversedArray));
    reversedArray = Util.reverseArray(evenArray1, 1);
    assertTrue("Reverse array of even length starting at index 1", Arrays.equals(evenArray1, expEvenArray1));
    assertTrue("Reverse array of even length starting at index 1 (returned array)", (evenArray1==reversedArray));

    System.arraycopy(oddArray0, 0, oddArray1, 0, oddArray0.length);
    reversedArray = Util.reverseArray(oddArray0, 0);
    assertTrue("Reverse array of odd length starting at index 0", Arrays.equals(oddArray0, expOddArray0));
    assertTrue("Reverse array of odd length starting at index 0 (returned array)", (oddArray0==reversedArray));
    reversedArray = Util.reverseArray(oddArray1, 1);
    assertTrue("Reverse array of odd length starting at index 1", Arrays.equals(oddArray1, expOddArray1));
    assertTrue("Reverse array of odd length starting at index 1 (returned array)", (oddArray1==reversedArray));
  }


  private static class InnerClass { } // used in testGetClassName()

  public void testGetClassName() {
    Object anon = new Object() { };
    String name = anon.getClass().getName();
    name = name.substring(name.indexOf("UtilTest")); // Should be "UtilTest$xxx" where xxx is a number

    Object[] TESTS = { new String(),     "String",
                       new Table(),      "Table",
                       null,             null,
                       anon,             name,
                       new InnerClass(), "InnerClass" };

    for (int i = 0; i < TESTS.length; i+=2) {
      String clazz = (TESTS[i] == null)? null: TESTS[i].getClass().getName();
      assertEquals("Util.getClassName('"+clazz+"')", TESTS[i+1], Util.getClassName(TESTS[i]));
    }
  }

  public void testGetConstructor() throws Exception {
    Constructor<?> const1 = Util.getConstructor("java.lang.StringBuffer()",                  false);
    Constructor<?> const2 = Util.getConstructor("java.lang.StringBuffer(java.lang.String)",  false);
    Constructor<?> const3 = Util.getConstructor("java.lang.StringBuffer(nxm.sys.lib.Shell)", false);

    StringBuffer buf1 = (StringBuffer)const1.newInstance(new Object[0]);
    StringBuffer buf2 = (StringBuffer)const2.newInstance(new Object[] { "abc" });

    assertEquals("Util.getConstructor('java.lang.StringBuffer()',                  false)", "",    buf1.toString());
    assertEquals("Util.getConstructor('java.lang.StringBuffer(java.lang.String)',  false)", "abc", buf2.toString());
    assertEquals("Util.getConstructor('java.lang.StringBuffer(nxm.sys.lib.Shell)', false)", null,  const3);

    boolean pass = false;
    try {
      Util.getConstructor("java.lang.StringBuffer(nxm.sys.lib.Shell)", true);
    } catch (Exception e) {
      pass = true;
    }
    assertTrue("Util.getConstructor('java.lang.StringBuffer(nxm.sys.lib.Shell)', true )", pass);
  }

  public void testGetMethod() throws Exception {
    Method method1 = Util.getMethod("java.lang.StringBuffer.toString()",                   false);
    Method method2 = Util.getMethod("java.lang.StringBuffer.append(java.lang.Object)",     false);
    Method method3 = Util.getMethod("java.lang.StringBuffer.append(char[])",               false);
    Method method4 = Util.getMethod("java.lang.StringBuffer.getChars(int,int,char[],int)", false);
    Method method5 = Util.getMethod("java.lang.StringBuffer.getChars(nxm.sys.lib.Shell)",  false);

    StringBuffer buf1  = new StringBuffer("abc");
    char[]       chars = new char[] { 'x', 'y', 'z' };
    char[]       array = new char[3];


    Object val1 = method1.invoke(buf1, new Object[0]).toString();
    Object val2 = method2.invoke(buf1, new Object[] { "123" }).toString();
    Object val3 = method3.invoke(buf1, new Object[] { chars }).toString();
    Object val4 = method4.invoke(buf1, new Object[] { Integer.valueOf(6), Integer.valueOf(9), array, Integer.valueOf(0) });

    assertEquals("Util.getMethod('java.lang.StringBuffer.toString()',                   false)", "abc",       val1);
    assertEquals("Util.getMethod('java.lang.StringBuffer.append(java.lang.Object)',     false)", "abc123",    val2);
    assertEquals("Util.getMethod('java.lang.StringBuffer.append(char[])',               false)", "abc123xyz", val3);
    assertEquals("Util.getMethod('java.lang.StringBuffer.getChars(int,int,char[],int)', false)", null,        val4); // void method
    assertArrayEquals("Util.getMethod('java.lang.StringBuffer.getChars(int,int,char[],int)', false)", chars,  array);
    assertEquals("Util.getMethod('java.lang.StringBuffer.getChars(nxm.sys.lib.Shell)',  false)", null,        method5);

    boolean pass = false;
    try {
      Util.getMethod("java.lang.StringBuffer.getChars(nxm.sys.lib.Shell)", true);
    } catch (Exception e) {
      pass = true;
    }
    assertTrue("Util.getMethod('java.lang.StringBuffer.getChars(nxm.sys.lib.Shell)',  true )", pass);


    String[] signatures = {
      "java.lang.StringBuffer.getChars(int,int,char[],int)",
      "java.lang.StringBuffer.getChars (int,int,char[],int) ",
      "java.lang.StringBuffer.getChars(int, int, char[], int)",
      "java.lang.StringBuffer.getChars ( int , int , char[] , int )",
    };
    for (int i = 0; i < signatures.length; i++) {
      Method m = Util.getMethod(signatures[i], false);
      assertEquals("Util.getMethod('"+signatures+"', false)", method4, m);
    }
  }

  public void testGetField() throws Exception {
    final Integer MAX  = Integer.valueOf(Integer.MAX_VALUE);
    final Integer MIN  = Integer.valueOf(Integer.MIN_VALUE);
    final Integer ZERO = Integer.valueOf(0);

    Field field1 = Util.getField("java.lang.Integer.MAX_VALUE",                 false);
    Field field2 = Util.getField("java.lang.Integer.MIN_VALUE",                 false);
    Field field3 = Util.getField("java.lang.Integer.FIELD_THAT_DOES_NOT_EXIST", false);

    assertEquals("Util.getMethod('java.lang.Integer.MAX_VALUE',                 false)", MAX,  field1.get(ZERO));
    assertEquals("Util.getMethod('java.lang.Integer.MIN_VALUE',                 false)", MIN,  field2.get(ZERO));
    assertEquals("Util.getMethod('java.lang.Integer.FIELD_THAT_DOES_NOT_EXIST', false)", null, field3);

    boolean pass = false;
    try {
      Util.getField("java.lang.Integer.FIELD_THAT_DOES_NOT_EXIST", true);
    } catch (Exception e) {
      pass = true;
    }
    assertTrue("Util.getMethod('java.lang.Integer.FIELD_THAT_DOES_NOT_EXIST',  true )", pass);
  }

  public static enum eColor {Red, Green, Blue};
  public void testValueOf () {
    eColor c = Util.valueOf(eColor.class, "Red");
    assertEquals("valueOf(Red) = Red", eColor.Red, c);

    c = Util.valueOf(eColor.class, "RED");
    assertEquals("valueOf(Red) = RED", eColor.Red, c);

    c = Util.valueOf(eColor.class, "Green");
    assertEquals("valueOf(Red) = Green", eColor.Green, c);

    c = Util.valueOf(eColor.class, "GREEN");
    assertEquals("valueOf(Red) = GREEN", eColor.Green, c);

    c = Util.valueOf(eColor.class, "Blue");
    assertEquals("valueOf(Red) = Blue", eColor.Blue, c);

    c = Util.valueOf(eColor.class, "BLUE");
    assertEquals("valueOf(Red) = BLUE", eColor.Blue, c);

    try {
      c = Util.valueOf(eColor.class, "Yellow");
      assertTrue("Line above should have thrown an exception", false);
    } catch (Exception e) {
      assertTrue("Line above should have thrown an exception", true);
    }
  }

  public void testGetShortClassName() {
    assertEquals("Should be the classname", "Shell", Util.getShortClassName(Shell.class));
    assertEquals("Should be null", null, Util.getShortClassName(null));
  }

  public void testFirst () {

    LinkedList<Integer> linklist = new LinkedList<>();
    ArrayList<Integer> arrayList = new ArrayList<>();
    SortedSet<Integer> sortedSet = new TreeSet<>();
    Queue<Integer> queue = new LinkedBlockingQueue<>();
    assertNull(Util.first(null));
    assertNull(Util.first(arrayList));

    arrayList.add(1);
    arrayList.add(0);
    arrayList.add(2);
    arrayList.add(4);
    arrayList.add(3);
    queue.addAll(arrayList);
    linklist.addAll(arrayList);
    sortedSet.addAll(arrayList);
    assertEquals(Integer.valueOf(0), Integer.valueOf(Util.first(sortedSet)));
    assertEquals(Integer.valueOf(1), Integer.valueOf(Util.first(arrayList)));
    assertEquals(Integer.valueOf(1), Integer.valueOf(Util.first(linklist)));
    assertEquals(Integer.valueOf(1), Integer.valueOf(Util.first(queue)));
  }

  public void testLast() {
    LinkedList<Integer> linklist = new LinkedList<>();
    ArrayList<Integer> arrayList = new ArrayList<>();
    SortedSet<Integer> sortedSet = new TreeSet<>();
    Queue<Integer> queue = new LinkedBlockingQueue<>();
    assertNull(Util.last(null));
    assertNull(Util.last(arrayList));
    arrayList.add(1);
    arrayList.add(0);
    arrayList.add(2);
    arrayList.add(4);
    arrayList.add(3);
    queue.addAll(arrayList);

    linklist.addAll(arrayList);
    sortedSet.addAll(arrayList);
    assertEquals(Integer.valueOf(4), Integer.valueOf(Util.last(sortedSet)));
    assertEquals(Integer.valueOf(3), Integer.valueOf(Util.last(arrayList)));
    assertEquals(Integer.valueOf(3), Integer.valueOf(Util.last(linklist)));
    assertEquals(Integer.valueOf(3), Integer.valueOf(Util.last(queue)));

  }

  public void testAsSet () {
    Set<Integer> testSet = new HashSet<>();
    testSet.add(1);
    testSet.add(7);
    testSet.add(0);
    testSet.add(3);
    Set<Integer> resSet = Util.asSet(1,7,0,3);
    if(resSet.size()!=testSet.size()) fail("Util.asSortedSet() returned a set of a different size");
    if(!testSet.containsAll(resSet)) fail("expected = " +testSet + " actual = "+resSet);
  }

  public void testAsSortedSet () {
    Set<Integer> testSet = new HashSet<>();
    testSet.add(0);
    testSet.add(1);
    testSet.add(3);
    testSet.add(7);
    Set<Integer> resSet =  Util.asSortedSet(1,7,0,3);
    Iterator<Integer> iteratorTestSet = testSet.iterator();
    Iterator<Integer> iteratorResSet = testSet.iterator();
    if(resSet.size()!=testSet.size()) fail("Util.asSortedSet() returned a set of a different size");
    while(iteratorTestSet.hasNext() && iteratorResSet.hasNext()) {
      assertEquals("The values should be equal as the set is sorted", iteratorTestSet.next(), iteratorResSet.next());
    }
  }

  private boolean threadFactorySuccessful;
  public void testNewThreadFactory () {
    ThreadFactory tf = Util.newThreadFactory("TestThreadFactory");
    Thread t = tf.newThread(new Runnable() {

      @Override
      public void run () {
        // if thread factory was created, this thread was created by it.
        threadFactorySuccessful=true;
      }
    });
    t.start(); // run the thread
    Time.sleep(.2); // give it some time, may not need this but just to be safe.
    assertTrue("Thread factory has worked correctly", threadFactorySuccessful);
  }

  /** This also tests {@link Util#round(Number, int)}
   *  and {@link Util#round(Number, int, java.math.RoundingMode, boolean, int)}
   */
  public void testScale () {
    byte byteTest = 127;
    short shortTest = 255;
    int intTest = 2450;
    long longTest = 1234567891;
    AtomicInteger atomicIntTest = new AtomicInteger(1422);
    AtomicLong atmoicLongTest = new AtomicLong(1422);
    double doubleTest = 1234.1234;
    float floatTest = 14.14f;
    BigDecimal bdTest = new BigDecimal(".01234567854897432468574");
    BigInteger bgIntegerTest = new BigInteger("123432156434654354351354535");
    int roundingSpaces = 3;
    // Scale > 0
    assertEquals("Should not round as these are whole numbers", byteTest, Util.scale(byteTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers",shortTest, Util.scale(shortTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers",intTest, Util.scale(intTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers",longTest, Util.scale(longTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers",atomicIntTest, Util.scale(atomicIntTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers",atmoicLongTest, Util.scale(atmoicLongTest, roundingSpaces));
    assertEquals("Should not round as these are whole numbers", 0,new BigDecimal(bgIntegerTest).compareTo((BigDecimal)Util.scale(bgIntegerTest, roundingSpaces)));
    assertEquals("Should round to the nearest thousandth", 0, BigDecimal.valueOf(1234.123).compareTo((BigDecimal)Util.scale(doubleTest, roundingSpaces)));
    assertEquals("Should round to the nearest thousandth", 0, BigDecimal.valueOf(14.140).compareTo((BigDecimal) Util.scale(floatTest, roundingSpaces)));
    assertEquals("Should round to the nearest thousandth", 0, new BigDecimal("0.012").compareTo((BigDecimal) Util.scale(bdTest, roundingSpaces)));
    assertEquals("Should round the last decimal up", 0, new BigDecimal("123456789.124").compareTo((BigDecimal) Util.scale(new CustomNumber("123456789.123956789"), roundingSpaces)));

    // Edge Cases - infinity, custom number type, NaN
    try {
      Util.round(new CustomNumber("adsfasdf"), 10);

      fail("Exception should have been thrown");
    }
    catch (NumberFormatException e) {
      // Do nothing, we are expecting an exception
    }
    assertEquals("Custom Number type pos infinity should convert to Double.POSITIVE_INFINITY.", Double.POSITIVE_INFINITY, Util.round(new CustomNumber(String.valueOf(Double.POSITIVE_INFINITY)),0));
    assertEquals("Custom Number type NaN should convert to Double.NaN.", Double.NaN,Util.round(new CustomNumber(String.valueOf(Double.NaN)),0));

    assertEquals("Should round the whole numbers based on the negative scale",0, new BigDecimal("123456800").compareTo((BigDecimal) Util.round(123456789.01423, -2)));


    // Scale < 0
    roundingSpaces = -2;
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("100").compareTo((BigDecimal) Util.scale(byteTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("300").compareTo((BigDecimal) Util.scale(shortTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("2400").compareTo((BigDecimal) Util.scale(intTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("1234567900").compareTo((BigDecimal) Util.scale(longTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("1400").compareTo((BigDecimal) Util.scale(atomicIntTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("1400").compareTo((BigDecimal) Util.scale(atmoicLongTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("123432156434654354351354500").compareTo((BigDecimal)Util.scale(bgIntegerTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("1200").compareTo((BigDecimal)Util.scale(doubleTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal(0).compareTo((BigDecimal) Util.scale(floatTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("0").compareTo((BigDecimal) Util.scale(bdTest, roundingSpaces)));
    assertEquals("Should round to the nearest hundred", 0, new BigDecimal("123456800").compareTo((BigDecimal) Util.scale(new CustomNumber("123456789.123956789"), roundingSpaces)));
  }

  public void testCurrentTimeNano () {
    for (int i = 0; i< 10000; i++) {
      long ms1 = System.currentTimeMillis();
      long ns1 = Util.currentTimeNano();
      long ns2 = Util.currentTimeNano();
      long ms2 = System.currentTimeMillis();
      String msg = ms1+"000000 <= "+ns1+" <= "+ms2+"999999";

      assertTrue(msg, ms1*Util.NS_FACTOR <= ns1);
      assertTrue(msg, ns1 <= ns1);
      assertTrue(msg, ns2/Util.NS_FACTOR <= ms2);

    }
  }

  private class CustomNumber extends Number {
    public CustomNumber (String number) {
      this.number = number;
    }

    private static final long serialVersionUID = 1L;
    private String number;

    @Override
    public int intValue () {
      return Integer.valueOf(number);
    }

    @Override
    public long longValue () {
      return Long.valueOf(number);
    }

    @Override
    public float floatValue () {
      return Float.valueOf(number);
    }

    @Override
    public double doubleValue () {
      return Double.valueOf(number);
    }

    @Override
    public String toString () {
      return number;
    }
  }
}
