package nxm.sys.test.junit.lib;

import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.junit.After;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.Midas;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;
import nxm.sys.lib.XMValue;
import nxm.sys.test.junit.NMTestCase;

/** This will test the Convert class.

    @author  J. Czechowski IV
 */
public final class ConvertTest extends NMTestCase implements TestValues, DataTypes {
  Midas M = getMidas();
  String currentOptions = (M!=null) ? M.io.getOptions() : "";

  /** public constructor to allow running this test case directly with JUnit. */
  public ConvertTest (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 testTo use when running the tests.
   */
  public ConvertTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  /**
   * This is called after each test. It will do any necessary cleanup.
   */
  @After
  public void tearDown () {
    M.io.setOptions(currentOptions);
  }

  //////////////////////////////////////////////////////////////////////////////
  public void testS2D () {
    /** Strings that are NaN values. */
    String[] nanStrings      = { "NaN", "nan", "NAN", "nAn" };
    /** Strings that are Inf values. (\u221E = INFINITY) */
    String[] infStrings      = { "Inf", "INF", "inf", "Infinity", "INFINITY",
                               "infinity","-Inf", "-INF", "-Infinity", "-infinity" };

    for (int i=0; i< infStrings.length; i++) {
      assertEquals("s2d("+infStrings[i]+") = Infinity", true, Double.isInfinite(Convert.s2d(infStrings[i])) );
    }
    for (int i=0; i< nanStrings.length; i++) {
      assertEquals("s2d("+nanStrings[i]+") = NaN", true, Double.isNaN(Convert.s2d(nanStrings[i]))      );
    }
  }

  public void testDms2deg () {
    //                        deg  min  sec
    double[][] values = { {  100d,  7d, 25.00d },
                          { -100d,  3d, 00.01d },
                          {    0d,  0d, 00.00d },
                          {   40d, 57d, 48.39d },
                          { -120d, 31d, 40.72d },
                          { -120d, 31d, 40.729368d } };
    double DELTA = 0.0; // Tolerance for rounding errors.

    for(int i = 0; i < values.length; i++) {
      double expected = 0.0;
      if(values[i][0] < 0) {
        expected = values[i][0] - values[i][1]/60.0 - values[i][2]/3600.0;
      } else {
        expected = values[i][0] + values[i][1]/60.0 + values[i][2]/3600.0;
      }
      String str      = "" + (int)values[i][0] + "'" + (int)values[i][1] + "'" + + values[i][2];

      assertEquals("dms2deg("+str+")",  expected,  Convert.dms2deg(str), DELTA);
    }

  }

  public void testDeg2dms () {
    assertEquals("deg2dms(24.43333)",  "24'26'00",      Convert.deg2dms(24.43333));         // Bug 400
    assertEquals("deg2dms(24.43333)",  "24'25'59.99",   Convert.deg2dms(24.43333,"00.00")); // Bug 400
    assertEquals("deg2dms(24.99998)",  "25'00'00",      Convert.deg2dms(24.99998));         // Bug 400
    assertEquals("deg2dms(24.99998)",  "24'59'59.93",   Convert.deg2dms(24.99998,"00.00")); // Bug 400
    assertEquals("deg2dms(100.1234)",  "100'07'25",     Convert.deg2dms(100.1236));
    assertEquals("deg2dms(-100.1234)", "-100'07'25",    Convert.deg2dms(-100.1236));
    assertEquals("deg2dms(100.1234)",  "100:07:25",     Convert.deg2dms(100.1236,':',':'));
    assertEquals("deg2dms(100.1234)",  "100:07:24.24",  Convert.deg2dms(100.1234,":",":","00.00"));
    assertEquals("deg2dms(100.1234)",  "100'07''24.24", Convert.deg2dms(100.1234,"'","''","00.00"));
    String[] values = { "00'00'01", "30'10'20", "-100'20'09" };

    for(int i = 0; i < values.length; i++) {
      assertEquals("dms2deg/deg2dms "+values[i],  values[i], Convert.deg2dms(Convert.dms2deg(values[i])));
    }
  }

  public void testIsFalseString() { } // tested in testIsTrueString()

  public void testIsTrueString() {
    for (int i = 0; i < TRUE_STATES.length; i++) {
      String str = TRUE_STATES[i].toUpperCase();
      assertEquals("isTrueString("+str+")",  true,  Convert.isTrueString(str));
      assertEquals("isFalseString("+str+")", false, Convert.isFalseString(str));
    }
    for (int i = 0; i < FALSE_STATES.length; i++) {
      String str = FALSE_STATES[i].toUpperCase();
      assertEquals("isTrueString("+str+")",  false, Convert.isTrueString(str));
      assertEquals("isFalseString("+str+")", true,  Convert.isFalseString(str));
    }
    for (int i = 0; i < JUNK_STATES.length; i++) {
      String str = JUNK_STATES[i].toUpperCase();
      assertEquals("isTrueString("+str+")",  false, Convert.isTrueString(str));
      assertEquals("isFalseString("+str+")", false, Convert.isFalseString(str));
    }
  }

  public void testS2state() {
    for (int i = 0; i < TRUE_S2STATE.length; i++) {
      String str = TRUE_S2STATE[i].toUpperCase();
      assertEquals("s2state("+str+")", true,  Convert.s2state(str, false));
      assertEquals("s2state("+str+")", true,  Convert.s2state(str, true));
    }
    for (int i = 0; i < FALSE_S2STATE.length; i++) {
      String str = FALSE_S2STATE[i].toUpperCase();
      assertEquals("s2state("+str+")", false, Convert.s2state(str, false));
      assertEquals("s2state("+str+")", false, Convert.s2state(str, true));
    }
    for (int i = 0; i < JUNK_S2STATE.length; i++) {
      String str = JUNK_S2STATE[i].toUpperCase();
      assertEquals("s2state("+str+")", false, Convert.s2state(str, false));
      assertEquals("s2state("+str+")", true,  Convert.s2state(str, true));
    }
  }

  public void testS2z() {
    for (int i = 0; i < TRUE_STRINGS.length; i++) {
      String str = TRUE_STRINGS[i].toUpperCase();
      assertEquals("s2z("+str+")",               true,  Convert.s2z(str));
      assertEquals("s2z("+str.toUpperCase()+")", true,  Convert.s2z(str.toUpperCase()));
      assertEquals("s2z("+str.toLowerCase()+")", true,  Convert.s2z(str.toLowerCase()));
      assertEquals("o2z("+str+")",               true,  Convert.o2z(str));
      assertEquals("o2z("+str.toUpperCase()+")", true,  Convert.o2z(str.toUpperCase()));
      assertEquals("o2z("+str.toLowerCase()+")", true,  Convert.o2z(str.toLowerCase()));
    }
    for (int i = 0; i < FALSE_STRINGS.length; i++) {
      String str = FALSE_STRINGS[i].toUpperCase();
      assertEquals("s2z("+str+")",               false, Convert.s2z(str));
      assertEquals("s2z("+str.toUpperCase()+")", false, Convert.s2z(str.toUpperCase()));
      assertEquals("s2z("+str.toLowerCase()+")", false, Convert.s2z(str.toLowerCase()));
      assertEquals("o2z("+str+")",               false, Convert.o2z(str));
      assertEquals("o2z("+str.toUpperCase()+")", false, Convert.o2z(str.toUpperCase()));
      assertEquals("o2z("+str.toLowerCase()+")", false, Convert.o2z(str.toLowerCase()));
    }
    // Special cases that differ from StringUtil
    assertEquals("s2z(null)", false, Convert.s2z(null));
    assertEquals("o2z(null)", false, Convert.o2z(null));
    assertEquals("s2z('')",   false, Convert.s2z(""));
    assertEquals("o2z('')",   false, Convert.o2z(""));

    for (int i = 0; i < JUNK_STRINGS.length; i++) {
      String str = JUNK_STRINGS[i];

      if (str == null)       continue; // Convert.s2z handles this different from StringUtil
      if (str.length() == 0) continue; // Convert.s2z handles this different from StringUtil

      assertException("s2z("+str+")", MidasException.class, new ConvertTaskS2Z(str));
      assertException("o2z("+str+")", MidasException.class, new ConvertTaskO2Z(str));
      if (str != null) {
        assertException("s2z("+str.toUpperCase()+")", MidasException.class, new ConvertTaskS2Z(str.toUpperCase()));
        assertException("s2z("+str.toLowerCase()+")", MidasException.class, new ConvertTaskS2Z(str.toLowerCase()));
        assertException("o2z("+str.toUpperCase()+")", MidasException.class, new ConvertTaskO2Z(str.toUpperCase()));
        assertException("o2z("+str.toLowerCase()+")", MidasException.class, new ConvertTaskO2Z(str.toLowerCase()));
      }
    }
  }

  public void testO2x() {
    // since 3.5.0 - o2x(..) support for numbers larger than 2^53 (Bug 2833)
    assertEquals("o2x(6362923255675387860)",  6362923255675387860L, Convert.o2x(6362923255675387860L));
    Data myXLData = Data.fromString("6362923255675387861", 'X', null);
    // since 3.5.1 - testing for (Bug 2833)
    assertEquals("o2x(myXLData,null) myXLData is a Data object",  6362923255675387861L, Convert.o2x(myXLData,null));
    assertEquals("o2x(myXLData) myXLData is a Data object",  6362923255675387861L, Convert.o2x(myXLData));
    assertEquals("o2x(null,null) null gives value of 0",  0, Convert.o2x(null,null));
    Time timeVal = new Time("2014:09:17::09:46:00");
    assertEquals("o2x(timeValue,null) timeValue is an Time",  2042099160L, Convert.o2x(timeVal,null));
    XMValue xmValue = new XMValue();
    xmValue.setFormatType('X');
    xmValue.setValue(6362923255675387863L);
    assertEquals("o2x(xmValue,null) xmValue is an XMValue",  6362923255675387863L, Convert.o2x(xmValue,null));
    assertEquals("o2x(Boolean.TRUE)",  1.0, Convert.o2x(Boolean.TRUE));
    assertEquals("o2x(Boolean.FALSE)", 0.0, Convert.o2x(Boolean.FALSE));
    assertEquals("o2x(obj) with value > Long.MAX_VALUE",  9223372036854775807L, Convert.o2x("9223372036854775810"));
    assertEquals("o2x(obj,null) with value < Long.MIN_VALUE",  -9223372036854775808L, Convert.o2x("-9223372036854775810", null));
    assertEquals("o2x(\"NaN\",null)",  0L, Convert.o2x("NaN",null));
    assertEquals("o2x(\"INFINITY\",null)", 9223372036854775807L, Convert.o2x("INFINITY", null));
    assertEquals("o2x(\"-INFINITY\")", -9223372036854775808L, Convert.o2x("-INFINITY"));
    assertEquals("o2x(6362923255675387860.5) rounds even (down)",  6362923255675387860L, Convert.o2x("6362923255675387860.5"));
    assertEquals("o2x(6362923255675387861.5) rounds even (up)",  6362923255675387862L, Convert.o2x("6362923255675387861.5"));
    assertEquals("o2x(6362923255675387860.6) rounds up",  6362923255675387861L, Convert.o2x("6362923255675387860.6"));
    assertEquals("o2x(6362923255675387861.4) rounds down",  6362923255675387861L, Convert.o2x("6362923255675387861.4"));

    // free up any native pointer
    xmValue.delete();
  }

  public void testO2d() {
    // TODO: other tests for o2d(..)

    // since 3.3.2 - o2d(Object) support for Boolean (Bug 2737)
    assertEquals("o2d(Boolean.TRUE)",  1.0, Convert.o2d(Boolean.TRUE));
    assertEquals("o2d(Boolean.FALSE)", 0.0, Convert.o2d(Boolean.FALSE));
  }

  /** tests s2n
   *  Specifically test the underlying getQuickNumber method used by s2n
   *  @since NeXtMidas 3.9.0
   */
  public void testS2n () {
    Number actual;
    // move options code below to the correct test TODO
    String doubleMax = String.valueOf(Double.MAX_VALUE);
    String integerMax = String.valueOf(Integer.MAX_VALUE);
    String longMax = String.valueOf(Long.MAX_VALUE);
    String doubleMin = "-"+doubleMax;
    String integerMin = String.valueOf(Integer.MIN_VALUE);
    String longMin = String.valueOf(Long.MIN_VALUE);
    double maxDoubleTrimmedBy2Decimals = 1.79769313486231E308;
    String strMaxDoubleTrimmedBy2Decimals = "1.79769313486231E308";

    if (true) {

    //****************************************************************************
    //************* NUMBERS getQuickNumber SHOULD CONVERT ************************
    //****************************************************************************

    //*****************************************************************************
    //        Test numbers at boundaries of Integer, Long, and Double
    //*****************************************************************************
    // test large enough that getQuickNumber will not initially realize it will fit in Integer
    actual = Convert.s2n(integerMax);
    assertEquals("s2n(Integer.MAX_VALUE) is Integer",  Integer.class, actual.getClass());
    assertEquals("s2n(Integer.MAX_VALUE) is Correct",  Integer.MAX_VALUE, actual);

    // test large enough that getQuickNumber will not initially realize it will fit in Long
    actual = Convert.s2n(longMax);
    assertEquals("s2n(Long.MAX_VALUE) is Long",  Long.class, actual.getClass());
    assertEquals("s2n(Long.MAX_VALUE) is Correct",  Long.MAX_VALUE, actual);

    // test large enough that getQuickNumber will not initially realize it will fit in Double
    actual = Convert.s2n(strMaxDoubleTrimmedBy2Decimals);
    assertEquals("s2n(Double.MAX_VALUE trimmed) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(Double.MAX_VALUE trimmed) is Correct", maxDoubleTrimmedBy2Decimals, actual);

    // test small enough that getQuickNumber will not initially realize it will fit in Integer
    actual = Convert.s2n(integerMin);
    assertEquals("s2n(Integer.MIN_VALUE) is Integer",  Integer.class, actual.getClass());
    assertEquals("s2n(Integer.MIN_VALUE) is Correct",  Integer.MIN_VALUE, actual);

    // test large enough that getQuickNumber will not initially realize it will fit in Long
    actual = Convert.s2n(longMin);
    assertEquals("s2n(Long.MIN_VALUE) is Long",  Long.class, actual.getClass());
    assertEquals("s2n(Long.MIN_VALUE) is Correct",  Long.MIN_VALUE, actual);

    // test large enough that getQuickNumber will not initially realize it will fit in Double
    actual = Convert.s2n("-"+strMaxDoubleTrimmedBy2Decimals);
    assertEquals("s2n(Double.MIN_VALUE trimmed) is BigDecimal",  Double.class, actual.getClass());
    assertEquals("s2n(Double.MIN_VALUE trimmed) is Correct", -maxDoubleTrimmedBy2Decimals, actual);


    //**********************************************************************************
    //    Test numbers so big or small they need to go into BigInteger or BigDecimal
    //**********************************************************************************
    actual = Convert.s2n("1.7976931348623167e+308");
    assertEquals("s2n() with value > Double.MAX_VALUE is BigDecimal", BigDecimal.class, actual.getClass());
    assertEquals("s2n() with value > Double.MAX_VALUE is correct", new BigDecimal("1.7976931348623167e+308"), actual);

    actual = Convert.s2n("-12345678901234567890123456789");
    assertEquals("s2n() with value < Long.MIN_VALUE is BigInteger", BigInteger.class, actual.getClass());
    assertEquals("s2n() with value < Long.MIN_VALUE is is correct", new BigInteger("-12345678901234567890123456789"), actual);

    //long belowMinInt = -9223372036854775L;
    //assertEquals("o2n() with value < Long.MIN_VALUE",  -9223372036854775L, Convert.o2n(Long.valueOf(belowMinInt)));

    //**********************************************************************************
    //    Test values represented as word that getQuickNumber should find
    //**********************************************************************************
    assertEquals("s2n(\"NaN\")", Double.NaN, Convert.s2n("NaN"));
    assertEquals("s2n(\"INFINITY\")", Double.POSITIVE_INFINITY, Convert.s2n("INFINITY",M,false));
    assertEquals("s2n(\"-INFINITY\")", Double.NEGATIVE_INFINITY, Convert.s2n("-INFINITY",M,false));

    //**********************************************************************************
    //    Test values that are a Result holding a number
    //**********************************************************************************
    M.getResults().put("DOUBLERESULT", 21474836470.4);
    M.getResults().put("LONGRESULT", 21474836470l);
    Number intDoubleResult = Convert.o2n("DOUBLERESULT", M, -1.0, false, true);
    Number intLongResult = Convert.o2n("LONGRESULT", M, -1, false, true);
    assertEquals("o2n(ResultHoldingDouble,M,-1,false,true) is Double", Double.class, intDoubleResult.getClass());
    assertEquals("o2n(ResultHoldingLong,M,-1,false,true) is Double", Long.class, intLongResult.getClass());
    assertEquals("o2n(ResultHoldingDouble... is Correct",  Double.valueOf(21474836470.4), intDoubleResult);
    assertEquals("o2n(ResultHoldingLong... is Correct",  Long.valueOf(21474836470l), intLongResult);

    //**********************************************************************************
    //    Test any number formats not tests above that getQuickNumber should find
    //  (1e-08, 2.14E+23, 0.0, .01, 3., etc.)
    //**********************************************************************************
    // lower case e
    actual = Convert.s2n("1e-08");
    assertEquals("s2n(1e-08) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(1e-08) is Correct",  Double.valueOf(1e-08), actual);

    // capital E
    actual = Convert.s2n("2.14E+23");
    assertEquals("s2n(2.14E+23) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(2.14E+23) is Correct",  Double.valueOf(2.14E+23), actual);

    // low value with high precision
    actual = Convert.s2n("0.00000000000000000000000000");
    assertEquals("s2n(0.00000000000000000000000000) is BigDecimal", BigDecimal.class, actual.getClass());
    assertEquals("s2n(0.00000000000000000000000000) is correct", new BigDecimal("0.00000000000000000000000000"), actual);

    // nothing before decimal
    actual = Convert.s2n(".01");
    assertEquals("s2n(.01) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(.01) is Correct",  Double.valueOf(.01), actual);

    // nothing after decimal
    actual = Convert.s2n("3.");
    assertEquals("s2n(3.) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(3.) is Correct",  Double.valueOf(3.), actual);

    // too large for Double - unusual format (multiple digits before decimal and no sign on exponent)
    actual = Convert.s2n("101.1e307");
    assertEquals("s2n(101.1e307) is BigDecimal",  BigDecimal.class, actual.getClass());
    assertEquals("s2n(101.1e307) is Correct",  new BigDecimal("101.1e307"), actual);

    // normal integer except leading '+'
    actual = Convert.s2n("+4321");
    assertEquals("s2n(+4321) is Integer",  Integer.class, actual.getClass());
    assertEquals("s2n(+4321) is Correct",  4321, actual);

    // normal long
    actual = Convert.s2n("123456789012");
    assertEquals("s2n(123456789012) is Long",  Long.class, actual.getClass());
    assertEquals("s2n(123456789012) is Correct",  123456789012L, actual);
    }

    // Test the speed
    //s2nMetrics();

    //****************************************************************************
    //******* SEE O2n FOR NUMBERS getQuickNumber SHOULD NOT CONVERT **************
    //****************************************************************************


    //****************************************************************************
    //******* Now Revert  **************
    //****************************************************************************
    M.io.setOptions("+DefaultToDoubleOnConvertO2nS2nEvenWhenPrecisionIsLost");
    //**********************************************************************************
    //    Test numbers so big or small they otherwise would have gone into BigInteger or BigDecimal
    //**********************************************************************************
    actual = Convert.s2n("1.7976931348623167e+308");
    assertEquals("s2n() with value > Double.MAX_VALUE is Double", Double.class, actual.getClass());
    assertEquals("s2n() with value > Double.MAX_VALUE is Infinity", Double.POSITIVE_INFINITY, actual);

    actual = Convert.s2n("-12345678901234567890123456789");
    assertEquals("s2n() with value < Long.MIN_VALUE is Double", Double.class, actual.getClass());
    assertEquals("s2n() with value < Long.MIN_VALUE has lost precision", -1.2345678901234568E28, actual);

    //**********************************************************************************
    //    Test Long that ends up being placed in Double
    //**********************************************************************************
    actual = Convert.s2n("123456789012");
    assertEquals("s2n(123456789012) is Double",  Double.class, actual.getClass());
    assertEquals("s2n(123456789012) is its double equivalent",  1.23456789012E11, actual);

    // Test the speed
    //s2nMetrics();

  }

  private void s2nMetrics () {
    String doubleMax = String.valueOf(Double.MAX_VALUE);
    String integerMax = String.valueOf(Integer.MAX_VALUE);
    String longMax = String.valueOf(Long.MAX_VALUE);
    String doubleMin = String.valueOf(-Double.MIN_VALUE);
    String integerMin = String.valueOf(Integer.MIN_VALUE);
    String longMin = String.valueOf(Long.MIN_VALUE);
    double maxDoubleTrimmedBy2Decimals = 1.79769313486231E308;
    String strMaxDoubleTrimmedBy2Decimals = "1.79769313486231E308";
    int metricsLoopCount = 10000;
    String infinity = "INFINITY";
    String negInfinity = "-INFINITY";
    runMetrics(() -> Convert.s2n(integerMax), "Convert.s2n(Integer.MAX_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(longMax), "Convert.s2n(Long.MAX_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(doubleMax), "Convert.s2n(Double.MAX_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(integerMin), "Convert.s2n(Integer.MIN_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(longMin), "Convert.s2n(Long.MIN_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(doubleMin), "Convert.s2n(Double.MIN_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n("1.7976931348623167e+308"), "Convert.s2n(Double.MAX_VALUE)", metricsLoopCount);
    runMetrics(() -> Convert.s2n("-12345678901234567890123456789"), "Convert.s2n(\"-12345678901234567890123456789\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n(negInfinity), "Convert.s2n(\"-INFINITY\",M,false)", metricsLoopCount);
    runMetrics(() -> Convert.s2n(infinity), "Convert.s2n(\"INFINITY\",M,false)", metricsLoopCount);
    runMetrics(() -> Convert.s2n("1e-08"), "Convert.s2n(\"1e-08\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n("2.14E+23"), "Convert.s2n(\"2.14E+23\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n("2.14E+23"), "Convert.s2n(\"2.14E+23\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n(".01"), "Convert.s2n(\".01\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n("3."), "Convert.s2n(\"3.\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n("101.1e307"), "Convert.s2n(\"101.1e307\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n(strMaxDoubleTrimmedBy2Decimals), "Convert.s2n(Double.MAX trimmed)", metricsLoopCount);
    runMetrics(() -> Convert.s2n("4321"), "Convert.s2n(\"4321\")", metricsLoopCount);
    runMetrics(() -> Convert.s2n("123456789012"), "Convert.s2n(\"123456789012\")", metricsLoopCount);
  }

  /** tests o2l
   *  tests both Java and Midas behavior options on too large values
   *  @since NeXtMidas 3.9.0 for NXM-3157
   */
  public void testO2l () {

    // test that +FavorJavaBehaviorOverTraditionalMidasBehavior causes too large numbers to wrap
    M.io.setOptions("+FavorJavaBehaviorOverTraditionalMidasBehavior"); // only meaningful  in o2l & s2l
    long wraps128Times = 0x4000000000L; // will wrap 128 times when placed in int
    assertEquals("o2l((0x400000000L) wrapped matching Java behavior", 0, Convert.o2l(wraps128Times));
    // test that the default is too large numbers saturate
    M.io.setOptions("-FavorJavaBehaviorOverTraditionalMidasBehavior"); // only meaningful  in o2l & s2l
    assertEquals("o2l((0x400000000L) default matches Midas behavior", Integer.MAX_VALUE, Convert.o2l(wraps128Times));
  }

  /** tests that o2lJava yields Java behavior */
  public void testO2lJava () {
    M.io.setOptions("+FavorJavaBehaviorOverTraditionalMidasBehavior"); // only meaningful  in o2l & s2l
    int expectedFromDouble = Integer.MAX_VALUE;
    int expectedFromLong = -10;
    Data doubleData = new Data(21474836470.4);
    Double javaDouble = Double.valueOf("21474836470.4");
    Data longData   = new Data(21474836470l);
    Long javaLong = Long.valueOf(21474836470l);
    M.getResults().put("DOUBLERESULT", 21474836470.4);
    M.getResults().put("LONGRESULT", 21474836470l);
    String doubleString = "21474836470.4";
    String longString = "21474836470";
    Time timeDouble = new Time(21474836470.4);
    XMValue xmValueDouble = new XMValue(21474836470.4);
    XMValue xmValueLong = new XMValue(21474836470l);
    int intDoubleData = Convert.o2lJava(doubleData, M);
    int intJavaDouble = Convert.o2lJava(javaDouble, M);
    int intLongData = Convert.o2lJava(longData, M);
    int intJavaLong = Convert.o2lJava(javaLong, M);
    int intDoubleResult = Convert.o2lJava("DOUBLERESULT", M);
    int intLongResult = Convert.o2lJava("LONGRESULT", M);
    int intDoubleStr = Convert.o2lJava(doubleString, M);
    int intLongStr = Convert.o2lJava(longString, M);
    int intTime = Convert.o2lJava(timeDouble, M);
    int intXMVALUEDouble = Convert.o2lJava(xmValueDouble, M);
    int intXMVALUELong = Convert.o2lJava(xmValueLong, M);
    assertEquals("o2lJava for Double Data object", expectedFromDouble, intDoubleData);
    assertEquals("o2lJava for Double Number object", expectedFromDouble, intJavaDouble);
    assertEquals("o2lJava for Double Result object", expectedFromDouble, intDoubleResult);
    assertEquals("o2lJava for Double String object", expectedFromDouble, intDoubleStr);
    assertEquals("o2lJava for Double Time object", expectedFromDouble, intTime);
    // If we are running from nextopt, we should not expect it to be java behavior
    if(!Shell.isNeXtOpt()) assertEquals("o2lJava for Double XMValue object", expectedFromDouble, intXMVALUEDouble);
    assertEquals("o2lJava for Long Data object", expectedFromLong, intLongData);
    assertEquals("o2lJava for Long Number object", expectedFromLong, intJavaLong);
    assertEquals("o2lJava for Long Result object", expectedFromLong, intLongResult);
    assertEquals("o2lJava for Long String object", expectedFromLong, intLongStr);
    if(!Shell.isNeXtOpt()) assertEquals("o2lJava for Long XMValue object", expectedFromLong, intXMVALUELong);
    M.io.setOptions("-FavorJavaBehaviorOverTraditionalMidasBehavior");

    XMValue.delete(xmValueDouble,xmValueLong); // free any native pointers
  }

  /** tests o2n
   *  tests both default and BigDecimal options
   *  @since NeXtMidas 3.9.0 for NXM-3157
   */
  public void testO2n () {

    Number actual;
    //*************************************************
    // Test with results names
    //***************************************************
    //**** not using BigDecimal flag  - small number****
    M.getResults().put("N246DOT8", 246.8);
    actual = Convert.o2n("N246DOT8",M,5.0,false,false);
    assertEquals("o2n(\"N1246DOT8\",M,5.0,false,false) is Double", Double.class, actual.getClass());
    assertEquals("o2n(\"N1246DOT8\",M,5.0,false,false) is Correct", 246.8, actual);
    //*** not using BigDecimal loses precision ***
    M.getResults().put("LONGMAX_1", Long.MAX_VALUE-1);
    actual = Convert.o2n("LONGMAX_1",M,5,false,false);
    assertEquals("o2n(Long.MAX_VALUE-1,M,5,false,false) is Long", Long.class, actual.getClass());
    boolean notLosePrecision = actual.longValue() == (Long.MAX_VALUE - 1);
    assertTrue("o2n(Long.MAX_VALUE,M,5,false,false) did not precision",notLosePrecision);
    // *** using BigDecimal keeps precision ***
    actual = Convert.o2n("LONGMAX_1",M,5,true,true);
    assertEquals("o2n(Long.MAX_VALUE-1,M,5,true,true) is BigDecimal", BigDecimal.class, actual.getClass());
    assertEquals("o2n(Long.MAX_VALUE-1,M,5,true,true) keeps precision", BigDecimal.valueOf(Long.MAX_VALUE-1),actual);

    //*************************************************
    // Test with various object types
    //***************************************************
    Object actualObject;
    Long seconds28NOV18_20503 = 2174516403L; // 20:03 on 11/28/18

    // Instant
    Instant numInstant = Instant.ofEpochSecond(seconds28NOV18_20503+Time.J1950TOJ1970);
    actualObject = Convert.o2n(numInstant,M,5,false,true);
    assertEquals("o2n(Instant) is Double", Double.class, actualObject.getClass());
    assertEquals("o2n(Instant) is correct", (double)seconds28NOV18_20503, actualObject);

    // Time - low precision so does not need BigDecimal
    Time lowPrecisionTime = new Time(2174516403L);
    actualObject = Convert.o2n(lowPrecisionTime,M,5,false,true);
    assertEquals("o2n(low precision Time) is Double", Double.class, actualObject.getClass());
    assertEquals("o2n(low precision Time) is correct", (double)seconds28NOV18_20503, actualObject);

    // Calendar
    GregorianCalendar numCalendar = lowPrecisionTime.toCalendar();
    actualObject = Convert.o2n(numCalendar,M,5,false,true);
    assertEquals("o2n(Calendar) is Double", Double.class, actualObject.getClass());
    assertEquals("o2n(Calendar) is correct", (double)seconds28NOV18_20503, actualObject);

    // XMLGregortianCalendar
    try {
      XMLGregorianCalendar numXMLCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(numCalendar);
      actualObject = Convert.o2n(numXMLCalendar,M,5,false,true);
      assertEquals("o2n(XMLGregorianCalendar) is Double", Double.class, actualObject.getClass());
      assertEquals("o2n(XMLGregorianCalendar) is correct", (double)seconds28NOV18_20503, actualObject);
    } catch (DatatypeConfigurationException e) {
      fail("Could not convertGregorianCalendar to XMLGregorianCalendar: "+e);
    }

    // Date
    Date numDate = lowPrecisionTime.toDateObject();
    actualObject = Convert.o2n(numDate,M,5,false,true);
    assertEquals("o2n(Date) is Double", Double.class, actualObject.getClass());
    assertEquals("o2n(Date) is correct", (double)seconds28NOV18_20503, actualObject);

    // Time - check BigDecimal on this one
    Time highPrecisionTime = lowPrecisionTime.addSec(0.123456789);
    actualObject = Convert.o2n(highPrecisionTime,M,5,false,true);
    assertEquals("o2n(high precision Time) is BigDecimal", BigDecimal.class, actualObject.getClass());
    assertEquals("o2n(high precision Time) is correct", new BigDecimal("2174516403.123456789"), ((BigDecimal)actualObject).stripTrailingZeros());

     //Data
    Data numData = new Data(12345678901234L);
    actualObject = Convert.o2n(numData,M,5,false,true);
    assertEquals("o2n(Data) is Long", Long.class, actualObject.getClass());
    assertEquals("o2n(Data) is correct", 12345678901234L, actualObject);

    // XmValue
    XMValue numXMValue = new XMValue(numData);
    actualObject = Convert.o2n(numXMValue,M,5,false,true);
    assertEquals("o2n(Data) is Long", Long.class, actualObject.getClass());
    assertEquals("o2n(Data) is correct", 12345678901234L, actualObject);
    numXMValue.delete(); // free any native pointers

    // Boolean
    boolean numBoolean = true;
    actualObject = Convert.o2n(numBoolean,M,5,false,true);
    assertEquals("o2n(Boolean) is Integer", Integer.class, actualObject.getClass());
    assertEquals("o2n(Boolean) is correct", 1, actualObject);

    // Test edge cases when sending in String value
    assertEquals("Convert.o2n(Integer.MAX_VALUE-1 is Integer", Integer.class, Convert.o2n(String.valueOf(Integer.MAX_VALUE-1)).getClass());
    assertEquals("Convert.o2n(\"-214123456123412342643213243\" is BigDecimal", BigInteger.class, Convert.o2n("-214123456123412342643213243").getClass());
    assertEquals("Convert.o2n(\"Integer.MAX_VALUE+1\" is long", Long.class, Convert.o2n(String.valueOf(Long.valueOf(Integer.MAX_VALUE)+1)).getClass());
    assertEquals("Convert.o2n(\"Long.MAX_VALUE+1\" is BigDecimal", BigInteger.class, Convert.o2n(String.valueOf(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE))).getClass());

    M.io.setOptions("+DefaultToDoubleOnConvertO2nS2nEvenWhenPrecisionIsLost");
    // Time - precision is truncated when forced to use Double
    actualObject = Convert.o2n(highPrecisionTime,M,5,false,true);
    assertEquals("o2n(high precision Time) is BigDecimal", Double.class, actualObject.getClass());
    assertEquals("o2n(high precision Time) is correct", 2174516403.123457, actualObject);
  }

  /** Tests o2tStrict which does a stricter check when converting String to Table.
   *  @since NeXtMidas 4.1.3
   */
  public void testO2tStrict() {
    Table testTable = new Table("{KEY1=hello,KEY2=hi}");
    Table testConvertedTable = Convert.o2tStrict(testTable);
    assertEquals("o2tStrict Table returns Table class",Table.class,testConvertedTable.getClass());
    assertEquals("o2tStrict Table returns right keys","hello",(String)testConvertedTable.get("KEY1"));

    // Check that Exception was thrown with malformed string
    assertException("o2tStrict with malformed string",MidasException.class,new ConvertTaskO2TStrict("{hello"));

    // Check that the returned Table is null
    assertNull("o2tStrict with integer",Convert.o2tStrict(2));
    assertNull("o2tStrict with null",Convert.o2tStrict(null));
  }

  public void testO2z () {
    // Basic tests included in testS2z()
    assertEquals("o2z(Boolean.TRUE)",  true,  Convert.o2z(Boolean.TRUE));
    assertEquals("o2z(Boolean.FALSE)", false, Convert.o2z(Boolean.FALSE));

    assertEquals("o2z(Integer.valueOf(1))", true,  Convert.o2z(Integer.valueOf(1)));
    assertEquals("o2z(Integer.valueOf(0))", false, Convert.o2z(Integer.valueOf(0)));
    assertException("o2z(Integer.valueOf(42))", MidasException.class, new ConvertTaskO2Z(Integer.valueOf(42)));
  }

  /** tests s2l
   *  tests both Java and Midas behavior options on too large values
   *  @since NeXtMidas 3.9.0 for NXM-3157
   */
  public void testS2l() {
    Midas M = getMidasContext();
    String currentOptions = M.io.getOptions();
    M.io.setOptions("+FavorJavaBehaviorOverTraditionalMidasBehavior");
    long wraps128Times = 0x4000000000L;
    assertEquals("s2l(\"0x400000000L\")",  0, Convert.s2l(String.valueOf(wraps128Times)));
    assertEquals("s2l() with value > Integer.MAX_VALUE",  -2147483648L, Convert.s2l("2147483648"));
    assertEquals("s2l(\"NaN\")",  0L, Convert.s2l("NaN"));
    assertEquals("s2l(\"INFINITY\")", -1, Convert.s2l("INFINITY"));
    assertEquals("s2l(\"-INFINITY\")", 0, Convert.s2l("-INFINITY"));
    M.io.setOptions(currentOptions);
  }

  public void testS2xl() {
    assertEquals("s2xl(\"6362923255675387866\")",  6362923255675387866L, Convert.s2xl("6362923255675387866"));
    assertEquals("s2xl() with value > Long.MAX_VALUE",  9223372036854775807L, Convert.s2xl("9223372036854775810"));
    assertEquals("o2x() with value < Long.MIN_VALUE",  -9223372036854775808L, Convert.s2xl("-9223372036854775810"));
    assertEquals("s2xl(\"NaN\")",  0L, Convert.s2xl("NaN"));
    assertEquals("s2xl(\"INFINITY\")", 9223372036854775807L, Convert.s2xl("INFINITY"));
    assertEquals("s2xl(\"-INFINITY\")", -9223372036854775808L, Convert.s2xl("-INFINITY"));
    assertEquals("s2xl(\"6362923255675387866.7\") truncates",  6362923255675387866L, Convert.s2xl("6362923255675387866.7"));
  }

  private static final class ConvertTaskS2Z implements Runnable {
    private final String val;

    public ConvertTaskS2Z(String v) { this.val = v; }

    public void run() { Convert.s2z(val); }
  }

  private static final class ConvertTaskO2Z implements Runnable {
    private final Object val;

    public ConvertTaskO2Z(Object v) { this.val = v; }

    public void run() { Convert.o2z(val); }
  }

  private static final class ConvertTaskO2TStrict implements Runnable {
    private final Object val;

    public ConvertTaskO2TStrict(Object v) { this.val = v; }

    public void run() { Convert.o2tStrict(val); }
  }


  public void testS2o() {
    assertEquals("s2o(null, \"C:\\test\\s2o\", null)", null, Convert.s2o(null, "C:\test", null)); // Bug 1260
  }

  public void testO2o() {
    _testBug415();
    _testNXM2931();

    assertEquals("o2o(null, \"C:\\test\\o2o\", null)", null, Convert.o2o(null, "C:\test", null)); // Bug 1260
  }

  /** These next tests are for Bug 415 (Convert.o2o has a problem when passed "35:21:66N".) */
  private void _testBug415() {
    Data aVal = new Data("35:21:66N");
    Data dVal = new Data(35.36833333333333);
    Data lVal = new Data(35);
    assertEquals("o2o(35:21:66N, S, null)", "35:21:66N",     Convert.o2o("35:21:66N", 'S', null));
    assertEquals("o2o(35:21:66N, A, null)", aVal.toString(), Convert.o2o("35:21:66N", 'A', null).toString());

    // The next two should cause an exception (Unable to convert 35:21:66N to type D. No applicable
    // conversion found.) unless we change how Convert.o2o works.
    //assertEquals("o2o(35:21:66N, D, null)", dVal.toString(), Convert.o2o("35:21:66N", 'D', null).toString());
    //assertEquals("o2o(35:21:66N, L, null)", lVal.toString(), Convert.o2o("35:21:66N", 'L', null).toString());
    try {
      Convert.o2o("35:21:66N", 'D', null);
      fail("o2o(35:21:66N, D, null) should cause an exception. No applicable applicable found.");
    } catch (Exception e) {
      assertTrue("o2o(35:21:66N, D, null) - No conversion.",
          e.getMessage().lastIndexOf("No applicable conversion found") != -1);
    }
    try {
      Convert.o2o("35:21:66N", 'L', null);
      fail("o2o(35:21:66N, L, null) should cause an exception. No applicable conversion found.");
    } catch (Exception e) {
      assertTrue("o2o(35:21:66N, L, null) - No conversion.",
          e.getMessage().lastIndexOf("No applicable conversion found") != -1);
    }
  }

  /** Test for NXM-2931.  Convert.o2o and Convert.s2o cannot convert "0\n1\n2" as a string
   *  since 3.7.1
   */
  private void _testNXM2931() {
    // test s2o, format is LONG
    try {
     Convert.s2o("0\n1\n2",'L', null);
     fail("s2o(0\n1\n2) should cause an exception. No applicable conversion found.");
    } catch (Exception e) {
      assertTrue("s2o(0\n1\n2,L,null) - Not converted.",
          e.getMessage().lastIndexOf("No applicable conversion found") != -1);
    }
    // test o2o, format is LONG
    try {
      Convert.o2o("0\n1\n2",'L', null);
      fail("o2o(0\n1\n2) should cause an exception. No applicable conversion found.");
    } catch (Exception e) {
      assertTrue("o2o(0\n1\n2,L,null) - Not converted.",
          e.getMessage().lastIndexOf("No applicable conversion found") != -1);
    }
    // test s2o, format is STRING
    try {
      Convert.s2o("0\n1\n2",'S', null);
     } catch (Exception e) {
       assertTrue("s2o(0\n1\n2,S,null) - Not converted.",
           e.getMessage().lastIndexOf("No applicable conversion found") != -1);
     }
     // test o2o, format is STRING
     try {
       Convert.o2o("0\n1\n2",'S', null);
     } catch (Exception e) {
       assertTrue("o2o(0\n1\n2,S,null) - Not converted.",
           e.getMessage().lastIndexOf("No applicable conversion found") != -1);
     }
     //test s2o without format
     try {
       Convert.s2o("0\n1\n2", null);
      } catch (Exception e) {
        assertTrue("s2o(0\n1\n2,S,null) - Not converted.",
            e.getMessage().lastIndexOf("No applicable conversion found") != -1);
      }
      // test o2o without format
      try {
        Convert.o2o("0\n1\n2",null);
      } catch (Exception e) {
        assertTrue("o2o(0\n1\n2,S,null) - Not converted.",
            e.getMessage().lastIndexOf("No applicable conversion found") != -1);
      }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Test byte buffer conversions
  //////////////////////////////////////////////////////////////////////////////////////////////////
  private static final boolean ieee = (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);

  private static final byte[]  TEST_BUFF = { (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04,
                                             (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
                                             (byte)0x10, (byte)0x20, (byte)0x30, (byte)0x40,
                                             (byte)0x50, (byte)0x60, (byte)0x70, (byte)0x80 };
  private static final long[]  XBUF_IEEE = { 0x0102030405060708L,            0x1020304050607080L };
  private static final int[]   LBUF_IEEE = { 0x01020304,     0x05060708,     0x10203040,     0x50607080 };
  private static final short[] IBUF_IEEE = { 0x0102, 0x0304, 0x0506, 0x0708, 0x1020, 0x3040, 0x5060, 0x7080 };
  private static final byte[]  BBUF_IEEE = TEST_BUFF;
  private static final byte[]  NBUF_IEEE = { 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00,
                                             0x05, 0x00, 0x06, 0x00, 0x07, 0x00, -8, 0x00,
                                             0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04,
                                             0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, -8 };
  /* 2.7.0 static final byte[] NBUF_IEEE = { 0x10, 0x00, 0x20, 0x00, 0x30, 0x00, 0x40, 0x00,
                                             0x50, 0x00, 0x60, 0x00, 0x70, 0x00, (byte)0x80, 0x00,
                                             0x00, 0x10, 0x00, 0x20, 0x00, 0x30, 0x00, 0x40,
                                             0x00, 0x50, 0x00, 0x60, 0x00, 0x70, 0x00, (byte)0x80 };
   */
  private static final double[] DBUF_IEEE = { 8.20788039913184e-304,   5.2136208375309800e-231 };
  private static final float[]  FBUF_IEEE = { 2.387939260590663e-38F,  6.301941157072183e-36F,
                                              3.1591606656767146e-29F, 1.5061877e10F };

  private static final long[]  XBUF_EEEI = { 0x0807060504030201L,            0x8070605040302010L };
  private static final int[]   LBUF_EEEI = { 0x04030201,     0x08070605,     0x40302010,     0x80706050 };
  private static final short[] IBUF_EEEI = { 0x0201, 0x0403, 0x0605, 0x0807, 0x2010, 0x4030, 0x6050, (short)0x8070 };
  private static final byte[]  BBUF_EEEI = BBUF_IEEE;
  private static final byte[]  NBUF_EEEI = NBUF_IEEE;
  private static final double[] DBUF_EEEI = { 5.447603722011605e-270, -1.4575323640233218e-306 };
  private static final float[]  FBUF_EEEI = { 1.539989614439558e-36F,  4.063216068939723e-34F,
                                              2.7519569396972656F,    -1.0320125984631408e-38F };

  private static final long[]  XBUF      = (ieee)? XBUF_IEEE: XBUF_EEEI;
  private static final int[]   LBUF      = (ieee)? LBUF_IEEE: LBUF_EEEI;
  private static final short[] IBUF      = (ieee)? IBUF_IEEE: IBUF_EEEI;
  private static final byte[]  BBUF      = (ieee)? BBUF_IEEE: BBUF_EEEI;
  private static final byte[]  NBUF      = (ieee)? NBUF_IEEE: NBUF_EEEI;
  private static final double[] DBUF     = (ieee)? DBUF_IEEE: DBUF_EEEI;
  private static final float[]  FBUF     = (ieee)? FBUF_IEEE: FBUF_EEEI;


  public void testBb2ja() {
    long[]  actX = new long[XBUF.length];
    int[]   actL = new int[LBUF.length];
    short[] actI = new short[IBUF.length];
    byte[]  actB = new byte[BBUF.length];
    byte[]  actN = new byte[NBUF.length];
    double[] actD = new double[DBUF.length];
    float[] actF = new float[FBUF.length];

    Convert.bb2ja(TEST_BUFF, 0, DataTypes.XLONG,  actX, 0, DataTypes.XLONG, XBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.LONG,   actL, 0, DataTypes.LONG,  LBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.INT,    actI, 0, DataTypes.INT,   IBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.BYTE,   actB, 0, DataTypes.BYTE,  BBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.NIBBLE, actN, 0, DataTypes.BYTE,  NBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.DOUBLE, actD, 0, DataTypes.DOUBLE, DBUF.length);
    Convert.bb2ja(TEST_BUFF, 0, DataTypes.FLOAT,  actF, 0, DataTypes.FLOAT,  FBUF.length);

    assertArrayEquals("bb2ja: Convert X to X", XBUF, actX);
    assertArrayEquals("bb2ja: Convert L to L", LBUF, actL);
    assertArrayEquals("bb2ja: Convert I to I", IBUF, actI);
    assertArrayEquals("bb2ja: Convert B to B", BBUF, actB);

    assertArrayEquals("bb2ja: Convert N to B", NBUF, actN);
    assertArrayEquals("bb2ja: Convert D to D", DBUF, actD);
    assertArrayEquals("bb2ja: Convert F to F", FBUF, actF);

    // test for BUG 2374 COMPARE: Fails when comparing type 3000 files
    boolean gotException;
    try {
      gotException = false;
      Convert.bb2ja(TEST_BUFF, 0, DataTypes.NONHOMOGENEOUS, actF, 0, DataTypes.FLOAT, FBUF.length);
    } catch (MidasException ex) {
      gotException = true;
    }
    assertTrue("bb2ja: Convert H to F (should throw exception)", gotException);

    try {
      gotException = false;
      Convert.bb2ja(TEST_BUFF, 0, DataTypes.LONG, actL, 0, DataTypes.NONHOMOGENEOUS, LBUF.length);
    } catch (MidasException ex) {
      gotException = true;
    }
    assertTrue("bb2ja: Convert L to H (should throw exception)", gotException);
    // end test for BUG 2374 COMPARE: Fails when comparing type 3000 files
  }

  /** Test Java Array to Byte Array (and the reverse)
   *  @since NextMidas 4.0.0
   */
  public void testJa2ba () {
    // java double
    double[] doubleArray = {123456000000.123456, 234567000000.234567, 7891011000000.7891011};
    byte[] doubleByte = Convert.ja2ba(doubleArray);
    double[] doubleCopy = new double[3];
    Convert.bb2ja(doubleByte, Convert.DOUBLE, doubleCopy, Convert.DOUBLE, 3);
    // java float
    float[] floatArray = {123456000000.123456f, 234567000000.234567f, 7891011000000.7891011f};
    byte[] floatByte = Convert.ja2ba(floatArray);
    float[] floatCopy = new float[3];
    Convert.bb2ja(floatByte, Convert.FLOAT, floatCopy, Convert.FLOAT, 3);
    // java long midas X
    long[] longArray = {123456000000l, 234567000000l, 7891011000000l};
    byte[] longByte = Convert.ja2ba(longArray);
    long[] longCopy = new long[3];
    Convert.bb2ja(longByte, Convert.XLONG, longCopy, Convert.XLONG, 3);
    assertArrayEquals("ja2ba -> bb2ja: Convert long[]", longArray, longCopy);
    // Java int midas L
    int[] intArray = {123456, 234567, 7891011};
    byte[] intByte = Convert.ja2ba(intArray);
    int[] intCopy = new int[3];
    Convert.bb2ja(intByte, Convert.LONG, intCopy, Convert.LONG, 3);
    assertArrayEquals("ja2ba -> bb2ja: Convert int[]", intArray, intCopy);
   // Java short midas I
    short[] shortArray = {1234, 2345, 7891};
    byte[] shortByte = Convert.ja2ba(shortArray);
    short[] shortCopy = new short[3];
    Convert.bb2ja(shortByte, Convert.INT, shortCopy, Convert.INT, 3);
    assertArrayEquals("ja2ba -> bb2ja: Convert short[]", shortArray, shortCopy);
  }

  public void testJa2bb() {
    byte[] actX = new byte[TEST_BUFF.length];
    byte[] actL = new byte[TEST_BUFF.length];
    byte[] actI = new byte[TEST_BUFF.length];
    byte[] actB = new byte[TEST_BUFF.length];
    byte[] actN = new byte[TEST_BUFF.length];
    byte[] actD = new byte[TEST_BUFF.length];
    byte[] actF = new byte[TEST_BUFF.length];

    Convert.ja2bb(XBUF, 0, DataTypes.XLONG, actX, 0, DataTypes.XLONG,  XBUF.length);
    Convert.ja2bb(LBUF, 0, DataTypes.LONG,  actL, 0, DataTypes.LONG,   LBUF.length);
    Convert.ja2bb(IBUF, 0, DataTypes.INT,   actI, 0, DataTypes.INT,    IBUF.length);
    Convert.ja2bb(BBUF, 0, DataTypes.BYTE,  actB, 0, DataTypes.BYTE,   BBUF.length);
    Convert.ja2bb(NBUF, 0, DataTypes.BYTE,  actN, 0, DataTypes.NIBBLE, NBUF.length);
    Convert.ja2bb(DBUF, 0, DataTypes.DOUBLE,actD, 0, DataTypes.DOUBLE, DBUF.length);
    Convert.ja2bb(FBUF, 0, DataTypes.FLOAT, actF, 0, DataTypes.FLOAT,  FBUF.length);

    assertArrayEquals("ja2bb: Convert X to X", TEST_BUFF, actX);
    assertArrayEquals("ja2bb: Convert L to L", TEST_BUFF, actL);
    assertArrayEquals("ja2bb: Convert I to I", TEST_BUFF, actI);
    assertArrayEquals("ja2bb: Convert B to B", TEST_BUFF, actB);
    assertArrayEquals("ja2bb: Convert B to N", TEST_BUFF, actN);
    assertArrayEquals("ja2bb: Convert D to D", TEST_BUFF, actD);
    assertArrayEquals("ja2bb: Convert F to F", TEST_BUFF, actF);

    // test for BUG 2374 COMPARE: Fails when comparing type 3000 files
    boolean gotException;
    try {
      gotException = false;
      Convert.ja2bb(FBUF, 0, DataTypes.NONHOMOGENEOUS, actF, 0, DataTypes.FLOAT,  FBUF.length);
    } catch (MidasException ex) {
      gotException = true;
    }
    assertTrue("ja2bb: Convert H to F (should throw exception)", gotException);

    try {
      gotException = false;
      Convert.ja2bb(BBUF, 0, DataTypes.BYTE, actB, 0, DataTypes.NONHOMOGENEOUS,  BBUF.length);
    } catch (MidasException ex) {
      gotException = true;
    }
    assertTrue("ja2bb: Convert B to H (should throw exception)", gotException);
   // end test for BUG 2374 COMPARE: Fails when comparing type 3000 files
  }

  /** Tests the various rounding modes used in Convert.type, Convert.bb2ja and Convert.ja2bb. */
  public void testRounding () {
    double[] values          = { 5.5, 2.5, 1.6, 1.1, 1.0, -1.0, -1.1, -1.6, -2.5, -5.5 }; // From java.math.RoundingMode
    double[] round_up        = {  6,   3,   2,   2,   1,   -1,   -2,   -2,   -3,   -6  }; // From java.math.RoundingMode
    double[] round_down      = {  5,   2,   1,   1,   1,   -1,   -1,   -1,   -2,   -5  }; // From java.math.RoundingMode
    double[] round_ceiling   = {  6,   3,   2,   2,   1,   -1,   -1,   -1,   -2,   -5  }; // From java.math.RoundingMode
    double[] round_floor     = {  5,   2,   1,   1,   1,   -1,   -2,   -2,   -3,   -6  }; // From java.math.RoundingMode
    double[] round_half_up   = {  6,   3,   2,   1,   1,   -1,   -1,   -2,   -3,   -6  }; // From java.math.RoundingMode
    double[] round_half_down = {  5,   2,   2,   1,   1,   -1,   -1,   -2,   -2,   -5  }; // From java.math.RoundingMode
    double[] round_half_even = {  6,   2,   2,   1,   1,   -1,   -1,   -2,   -2,   -6  }; // From java.math.RoundingMode
    double[] cast            = new double[values.length]; // Match behavior of casting to an integer
    double[] ceil            = new double[values.length]; // Match behavior of Math.ceil(..)
    double[] floor           = new double[values.length]; // Match behavior of Math.floor(..)
    double[] rint            = new double[values.length]; // Match behavior of Math.rint(..)
    double[] round           = new double[values.length]; // Match behavior of Math.round(..)
    double[] lround          = new double[values.length]; // Match behavior of Convert.lround(..)

    for (int i = 0; i < values.length; i++) {
      cast[i]   = (double)((int)values[i]);
      ceil[i]   = Math.ceil(values[i]);
      floor[i]  = Math.floor(values[i]);
      rint[i]   = Math.rint(values[i]);
      round[i]  = Math.round(values[i]);
      lround[i] = (int)Convert.lround(values[i]);
    }

    testRounding("ROUND_DOWN",        Convert.ROUND_DOWN,        values, round_down,      cast);
    testRounding("ROUND_CEILING",     Convert.ROUND_CEILING,     values, round_ceiling,   cast);
    testRounding("ROUND_FLOOR",       Convert.ROUND_FLOOR,       values, round_floor,     cast);
    testRounding("ROUND_HALF_EVEN",   Convert.ROUND_HALF_EVEN,   values, round_half_even, cast);
    testRounding("ROUND_HALF_UP",     Convert.ROUND_HALF_UP,     values, round_half_up,   cast);

    testRounding("ROUND_DOWN",        Convert.ROUND_DOWN,        values, cast,   cast);
    testRounding("ROUND_CEILING",     Convert.ROUND_CEILING,     values, ceil,   cast);
    testRounding("ROUND_FLOOR",       Convert.ROUND_FLOOR,       values, floor,  cast);
    testRounding("ROUND_HALF_EVEN",   Convert.ROUND_HALF_EVEN,   values, rint,   cast);
    testRounding("ROUND_ROUND",       Convert.ROUND_ROUND,       values, round,  cast);
    testRounding("ROUND_UNNECESSARY", Convert.ROUND_UNNECESSARY, values, null,   cast);
    testRounding("ROUND_HALF_UP",     Convert.ROUND_HALF_UP,     values, lround, cast);
  }

  /** Only used by testRounding() */
  private void testRounding (String mode, int round, double[] val, double[] exp, double[] cast) {
    if (exp != null) {
      testRounding(mode, round, 'D', 'D', val, val); // no change
      testRounding(mode, round, 'D', 'F', val, val); // no change
      testRounding(mode, round, 'D', 'X', val, exp); // rounded
      testRounding(mode, round, 'D', 'L', val, exp); // rounded
      testRounding(mode, round, 'D', 'I', val, exp); // rounded
      testRounding(mode, round, 'D', 'B', val, exp); // rounded

      testRounding(mode, round, 'F', 'D', val, val); // no change
      testRounding(mode, round, 'F', 'F', val, val); // no change
      testRounding(mode, round, 'F', 'X', val, exp); // rounded
      testRounding(mode, round, 'F', 'L', val, exp); // rounded
      testRounding(mode, round, 'F', 'I', val, exp); // rounded
      testRounding(mode, round, 'F', 'B', val, exp); // rounded
    }
    char[] types = { 'X', 'L', 'I', 'B' };
    for (int i = 0; i < types.length; i++) {
      char typ = types[i];
      testRounding(mode, round, typ, 'D', cast, cast); // truncated
      testRounding(mode, round, typ, 'F', cast, cast); // truncated
      testRounding(mode, round, typ, 'X', cast, cast); // truncated
      testRounding(mode, round, typ, 'L', cast, cast); // truncated
      testRounding(mode, round, typ, 'I', cast, cast); // truncated
      testRounding(mode, round, typ, 'B', cast, cast); // truncated
    }
  }

  /** Only used by testRounding() */
  private void testRounding (String mode, int round, char t1, char t2, double[] val, double[] exp) {
    int      len   = val.length;
    double[] act   = new double[len];
    byte[]   buf1  = new byte[len*8];
    byte[]   buf2  = new byte[len*8];
    byte[]   buf   = null;
    Object   in    = copy(t1,val);
    Object   out   = copy(t2,null,len);
    String   title = null;
    boolean  sat   = false;

    // Test Convert.type
    Convert.ja2bb(val, 0, (byte)'D', buf1, 0, (byte)t1,  len);
    Convert.type( buf1,0, (byte)t1,  buf2, 0, (byte)t2,  len, round, sat);
    Convert.bb2ja(buf2,0, (byte)t2,  act,  0, (byte)'D', len);
    assertArrayEquals("Convert.type: Rounding with "+mode+" "+t1+"->"+t2, exp, act, 0.00001);


    // Test Convert.ja2bb
    buf   = new byte[len*8];
    title = "Convert.ja2bb: Rounding with "+mode+" "+t1+"->"+t2;

    Convert.ja2bb(in,  0, (byte)t1, buf, 0, (byte)t2, len, round, sat);
    Convert.bb2ja(buf, 0, (byte)t2, out, 0, (byte)t2, len, round, sat);

    switch (t2) {
      case 'D': assertArrayEquals(title, (double[])copy(t2,exp), (double[])out, 0.00001d); break;
      case 'F': assertArrayEquals(title, (float[]) copy(t2,exp), (float[]) out, 0.00001f); break;
      case 'X': assertArrayEquals(title, (long[])  copy(t2,exp), (long[])  out);           break;
      case 'L': assertArrayEquals(title, (int[])   copy(t2,exp), (int[])   out);           break;
      case 'I': assertArrayEquals(title, (short[]) copy(t2,exp), (short[]) out);           break;
      case 'B': assertArrayEquals(title, (byte[])  copy(t2,exp), (byte[])  out);           break;
    }


    // Test Convert.bb2ja
    buf   = new byte[len*8];
    title = "Convert.bb2ja: Rounding with "+mode+" "+t1+"->"+t2;

    Convert.ja2bb(in,  0, (byte)t1, buf, 0, (byte)t1, len, round, sat);
    Convert.bb2ja(buf, 0, (byte)t1, out, 0, (byte)t2, len, round, sat);

    switch (t2) {
      case 'D': assertArrayEquals(title, (double[])copy(t2,exp), (double[])out, 0.00001d); break;
      case 'F': assertArrayEquals(title, (float[]) copy(t2,exp), (float[]) out, 0.00001f); break;
      case 'X': assertArrayEquals(title, (long[])  copy(t2,exp), (long[])  out);           break;
      case 'L': assertArrayEquals(title, (int[])   copy(t2,exp), (int[])   out);           break;
      case 'I': assertArrayEquals(title, (short[]) copy(t2,exp), (short[]) out);           break;
      case 'B': assertArrayEquals(title, (byte[])  copy(t2,exp), (byte[])  out);           break;
    }
  }

  /** Makes a copy of an array. */
  private static final Object copy (char type, double[] buf) {
    return copy(type, buf, buf.length);
  }

  /** Makes a copy of an array with given length. */
  private static final Object copy (char type, double[] buf, int len) {
    Object array = null;
    switch (type) {
      case 'D': array = new double[len]; break;
      case 'F': array = new float[len];  break;
      case 'X': array = new long[len];   break;
      case 'L': array = new int[len];    break;
      case 'I': array = new short[len];  break;
      case 'B': array = new byte[len];   break;
    }
    if (buf != null) {
      switch (type) {
        case 'D': for (int i = 0; i < len; i++) ((double[])array)[i] =         buf[i]; break;
        case 'F': for (int i = 0; i < len; i++) ((float[])array)[i]  = (float) buf[i]; break;
        case 'X': for (int i = 0; i < len; i++) ((long[])array)[i]   = (long)  buf[i]; break;
        case 'L': for (int i = 0; i < len; i++) ((int[])array)[i]    = (int)   buf[i]; break;
        case 'I': for (int i = 0; i < len; i++) ((short[])array)[i]  = (short) buf[i]; break;
        case 'B': for (int i = 0; i < len; i++) ((byte[])array)[i]   = (byte)  buf[i]; break;
      }
    }
    return array;
  }

  /**
   * Test Convert.o2List
   * @since NeXtMidas 3.7.1 for NXM-2945
   */
  public void testO2List () {
    // test Table.getList("KEY") returns Convert.o2List(getO("KEY"))
    Table testTable = new Table();

    // objects to convert to List
    String[] myString = {"A","B","C","D","E"};        // String array

    Data myDataObj = new Data(10,20,30,40);           // nxm Data object

    List<String> arrList = new ArrayList<>();         // array list of String
      arrList.add("cat");
      arrList.add("box");
      arrList.add("elephant");

    Object badObj = "what";                          // bad Object

    List<Integer> intList = new ArrayList<>();       // array list of Integer
      intList.add(22);
      intList.add(33);
      intList.add(88);

    List<String> linkedList = new LinkedList<>();    // linked list of String
      linkedList.add("hello");
      linkedList.add("world");
      linkedList.add("My");
      linkedList.add("is");
      linkedList.add("hal");

    List<Double> listD = new ArrayList<>(Arrays.asList(1.38, 2.56, 4.3));  // list of Double

    Double[] arrayD = {1.38, 2.56, 4.3};             // Double array

    List<String> strCol = Collections.emptyList();   // Collections of empty String

    List<Number> linkedNumbers = new LinkedList<>(); // linked list of Numbers ???
      linkedNumbers.add(Integer.valueOf(123));
      linkedNumbers.add(Float.valueOf((float)3.1415));
      linkedNumbers.add(Double.valueOf(299.988));
      linkedNumbers.add(Long.valueOf(67000));

    Object[] objArr = {"one","two","three"};        // simple Object array

    // o2List object[]
    testTable.put("myStringArray", myString);
    assertEquals("String array size equals String List size is ", myString.length, testTable.getList("myStringArray").size());
    for (int i = 0; i<myString.length; i++) {
      assertEquals("String "+myString[i]+" equals table.list " + testTable.getList("myStringArray").get(i),
          myString[i],testTable.getList("myStringArray").get(i));
    }

    // o2List Data
    testTable.put("myDataObj", myDataObj);
    assertEquals("Data  size equals table List size is ", myDataObj.size, testTable.getList("myDataObj").size());
    for (int i = 0; i<myDataObj.size; i++) {
      assertEquals("myDataObj "+myDataObj.getIndex(i)+" equals table.list " + testTable.getList("myDataObj").get(i),
          Long.parseLong(String.valueOf(myDataObj.getIndex(i))),Long.parseLong(String.valueOf(testTable.getList("myDataObj").get(i))));
    }

    // o2List LIST
    testTable.put("arrList", arrList);
    assertEquals("arrList array size equals table List size is ", arrList.size(), testTable.getList("arrList").size());
    for (int i = 0; i<arrList.size(); i++) {
      assertEquals("String "+arrList.get(i)+" equals table.list " + testTable.getList("arrList").get(i),
          arrList.get(i),testTable.getList("arrList").get(i));
    }

    // o2List MidasException
    try {
      testTable.put("badObj", badObj);
    } catch (Exception e) {
      assertThat(e.getMessage(), containsString("Convert.o2List() could not convert "));
    }

    // o2List LIST
    testTable.put("intList",intList);
    assertEquals("intList array size equals table List size is ", intList.size(), testTable.getList("intList").size());
    for (int i = 0; i<intList.size(); i++) {
      assertEquals("intList "+intList.get(i)+" equals table.list " + testTable.getList("intList").get(i),
          intList.get(i),testTable.getList("intList").get(i));
    }

    // o2List LIST
    testTable.put("linkedList", linkedList);
    assertEquals("linkedList array size equals table List size is ", linkedList.size(), testTable.getList("linkedList").size());
    for (int i = 0; i<intList.size(); i++) {
      assertEquals("linkedList "+linkedList.get(i)+" equals table.list " + testTable.getList("linkedList").get(i),
          linkedList.get(i),testTable.getList("linkedList").get(i));
    }

    // o2List LIST
    testTable.put("listD",listD);
    assertEquals("listD array size equals table List size is ", listD.size(), testTable.getList("listD").size());
    for (int i = 0; i<intList.size(); i++) {
      assertEquals("listD "+listD.get(i)+" equals table.list " + testTable.getList("listD").get(i),
          listD.get(i),testTable.getList("listD").get(i));
    }

    // o2List object[]
    testTable.put("arrayD", arrayD);
    assertEquals("listD array size equals table List size is ", arrayD.length, testTable.getList("arrayD").size());
    for (int i = 0; i<arrayD.length; i++) {
      assertEquals("arrayD "+arrayD[i]+" equals table.list " + testTable.getList("arrayD").get(i),
          arrayD[i],testTable.getList("arrayD").get(i));
    }

    // o2List LIST
    testTable.put("strCol",strCol);
    assertEquals("strCol array size equals table List size is ",strCol.size(),testTable.getList("strCol").size());

    // o2List LIST
    testTable.put("linkedNumbers",linkedNumbers);
    assertEquals("linkedNumbers array size equals table List size is ", linkedNumbers.size(), testTable.getList("linkedNumbers").size());
    for (int i = 0; i<arrayD.length; i++) {
      assertEquals("linkedNumbers "+linkedNumbers.get(i)+" equals table.list " + testTable.getList("linkedNumbers").get(i),
          linkedNumbers.get(i),testTable.getList("linkedNumbers").get(i));
    }

    // o2LIst object[]
    testTable.put("objArr", objArr);
    assertEquals("objArr array size equals table List size is ", objArr.length, testTable.getList("objArr").size());
    for (int i = 0; i<arrayD.length; i++) {
      assertEquals("objArr "+objArr[i]+" equals table.list " + testTable.getList("objArr").get(i),
          objArr[i],testTable.getList("objArr").get(i));
    }
  }

  public void testType() {
    // TBD - need tests for type 'P' and type 'N'

    double[] values1  = { 1d, 0d, -1d, 32768d, 3.14d, -3.14d, 32.96d };
    float[]  values2  = { 1f, 0f, -1f, 32768f, 3.14f, -3.14f, 32.96f };
    long[]   values3  = { 1L, 0L, -1L, 42, -42, 120,
                          Integer.MAX_VALUE,    Integer.MIN_VALUE,
                          Integer.MAX_VALUE+1L, Integer.MIN_VALUE-1L,
                          Long.MAX_VALUE,       Long.MIN_VALUE
                        };
    byte[]   values4  = { 1, 0, -1, 42, -42, 120 };
    double[] values5d = { 1.0d, 0.0d, -1.0d, 32768.0d, 3.14d, -3.14d, 32.96d,
                          Double.MAX_VALUE, -Double.MAX_VALUE,
                          Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
                        };
    float[]  values5f = { 1.0f, 0.0f, -1.0f, 32768.0f, 3.14f, -3.14f, 32.96f,
                          Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
                          Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY
                        };
    float[]  values6f = { 1.0f, 0.0f, -1.0f, 32768.0f, 3.14f, -3.14f, 32.96f,
                          Float.MAX_VALUE, Float.MIN_VALUE,
                          Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY
                        };
    double[] values6d = { 1.0d, 0.0d, -1.0d, 32768.0d, 3.14d, -3.14d, 32.96d,
                          Float.MAX_VALUE, Float.MIN_VALUE,
                          Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
                        };
    double[] values7  = { (double)Integer.MAX_VALUE,    (double)Integer.MIN_VALUE,
                          (double)Integer.MAX_VALUE+1L, (double)Integer.MIN_VALUE-1L,
                          (double)Long.MAX_VALUE,       (double)Long.MIN_VALUE
                        };

    final int maxLen = Math.max(Math.max(Math.max(values1.length,  values2.length ),
                                         Math.max(values3.length,  values4.length )),
                                Math.max(Math.max(values5d.length, values6d.length),
                                         Math.max(values7.length,  0)));

    double[] dbuf1  = new double[maxLen];
    float[]  fbuf1  = new float[maxLen];
    long[]   xbuf1  = new long[maxLen];
    int[]    lbuf1  = new int[maxLen];
    short[]  ibuf1  = new short[maxLen];
    byte[]   bbuf1  = new byte[maxLen];

    double[] dbuf2  = new double[maxLen];
    float[]  fbuf2  = new float[maxLen];
    long[]   xbuf2  = new long[maxLen];
    int[]    lbuf2  = new int[maxLen];
    short[]  ibuf2  = new short[maxLen];
    byte[]   bbuf2  = new byte[maxLen];


    // narrowing conversions
    for (int i = 0; i < maxLen; i++) {
      double val = (i < values1.length)? values1[i] : 0.0;
      dbuf1[i] =         val;
      fbuf1[i] = (float )val;
      xbuf1[i] =         Math.min(Long.MAX_VALUE,    Math.max(Long.MIN_VALUE,    Math.round(val)));
      lbuf1[i] = (int   )Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, Math.round(val)));
      ibuf1[i] = (short )Math.min(Short.MAX_VALUE,   Math.max(Short.MIN_VALUE,   Math.round(val)));
      bbuf1[i] = (byte  )Math.min(Byte.MAX_VALUE,    Math.max(Byte.MIN_VALUE,    Math.round(val)));
    }

    checkType(new Object[] { dbuf1 },
              new Object[] { dbuf1, fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { dbuf2, fbuf2, xbuf2, lbuf2, ibuf2, bbuf2 },
              values1.length);

    checkType(new Object[] { fbuf1 },
              new Object[] { fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { fbuf2, xbuf1, lbuf2, ibuf2, bbuf2 },
              values1.length);

    checkType(new Object[] { xbuf1 },
              new Object[] { xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { xbuf2, lbuf2, ibuf2, bbuf2 },
              values1.length);

    checkType(new Object[] { lbuf1 },
              new Object[] { lbuf1, ibuf1, bbuf1 },
              new Object[] { lbuf2, ibuf2, bbuf2 },
              values1.length);

    checkType(new Object[] { ibuf1 },
              new Object[] { ibuf1, bbuf1 },
              new Object[] { ibuf2, bbuf2 },
              values1.length);

    checkType(new Object[] { bbuf1 },
              new Object[] { bbuf1 },
              new Object[] { bbuf2 },
              values1.length);

    // float -> any, double -> any
    for (int i = 0; i < maxLen; i++) {
      float val = (i < values2.length)? values2[i] : 0f;
      dbuf1[i] = (double)val;
      fbuf1[i] =         val; // float
      xbuf1[i] =         Math.min(Long.MAX_VALUE,    Math.max(Long.MIN_VALUE,    Math.round(val))); // long
      lbuf1[i] =         Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, Math.round(val))); // int
      ibuf1[i] = (short )Math.min(Short.MAX_VALUE,   Math.max(Short.MIN_VALUE,   Math.round(val)));
      bbuf1[i] = (byte  )Math.min(Byte.MAX_VALUE,    Math.max(Byte.MIN_VALUE,    Math.round(val)));
    }

    checkType(new Object[] { dbuf1, fbuf1 },
              new Object[] { dbuf1, fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { dbuf2, fbuf2, xbuf2, lbuf2, ibuf2, bbuf2 },
              values2.length);

    // long -> any
    for (int i = 0; i < maxLen; i++) {
      long val = (i < values3.length)? values3[i] : 0L;
      dbuf1[i] = (double)val;
      fbuf1[i] = (float )val;
      xbuf1[i] =         Math.min(Long.MAX_VALUE,    Math.max(Long.MIN_VALUE,    val)); // long
      lbuf1[i] = (int   )Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, val)); // int
      ibuf1[i] = (short )Math.min(Short.MAX_VALUE,   Math.max(Short.MIN_VALUE,   val));
      bbuf1[i] = (byte  )Math.min(Byte.MAX_VALUE,    Math.max(Byte.MIN_VALUE,    val));
    }

    checkType(new Object[] { xbuf1 },
              new Object[] { dbuf1, fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { dbuf2, fbuf2, xbuf2, lbuf2, ibuf2, bbuf2 },
              values3.length);

    // any -> any (value in byte range)
    for (int i = 0; i < maxLen; i++) {
      byte val = (i < values4.length)? values4[i] : 0;
      dbuf1[i] = (double)val;
      fbuf1[i] = (float )val;
      xbuf1[i] = (long  )val;
      lbuf1[i] = (int   )val;
      ibuf1[i] = (short )val;
      bbuf1[i] =         val; // byte
    }

    checkType(new Object[] { dbuf1, fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { dbuf1, fbuf1, xbuf1, lbuf1, ibuf1, bbuf1 },
              new Object[] { dbuf2, fbuf2, xbuf2, lbuf2, ibuf2, bbuf2 },
              values4.length);

    // double -> double/float
    checkType(new Object[] { values5d },
              new Object[] { values5d, values5f },
              new Object[] { dbuf2, fbuf2 },
              values5d.length);

    // float -> double/float
    checkType(new Object[] { values6f },
              new Object[] { values6d, values6f },
              new Object[] { dbuf2, fbuf2 },
              values6d.length);

    // float -> any, double -> any
    for (int i = 0; i < maxLen; i++) {
      double val = (i < values7.length)? values7[i] : 0f;
      dbuf1[i] =         val; // double
      fbuf1[i] = (float )val;
      xbuf1[i] =         Math.min(Long.MAX_VALUE,    Math.max(Long.MIN_VALUE,    Math.round(val))); // long
      lbuf1[i] = (int   )Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, Math.round(val)));
      ibuf1[i] = (short )Math.min(Short.MAX_VALUE,   Math.max(Short.MIN_VALUE,   Math.round(val)));
      bbuf1[i] = (byte  )Math.min(Byte.MAX_VALUE,    Math.max(Byte.MIN_VALUE,    Math.round(val)));
    }

    checkType(new Object[] { dbuf1, fbuf1 },
              new Object[] { dbuf1, fbuf1, xbuf1 },
              new Object[] { dbuf2, fbuf2, xbuf2 },
              values7.length);

    // test for BUG 2374 COMPARE: Fails when comparing type 3000 files
    boolean gotException;
    try {
      gotException = false;
      Convert.type(bbuf1, 0, DataTypes.DOUBLE, bbuf2, 0, DataTypes.NONHOMOGENEOUS, 1);
    } catch (MidasException ex) {
      gotException = true;
    }
    assertTrue("type: Convert D to H (should throw exception)", gotException);

    try {
      gotException = false;
      Convert.type(bbuf1, 0, DataTypes.NONHOMOGENEOUS, bbuf2, 0, DataTypes.FLOAT, 1);
    } catch (MidasException iaex) {
      gotException = true;
    }
    assertTrue("type: Convert H to F (should throw exception)", gotException);
    // end test for BUG 2374 COMPARE: Fails when comparing type 3000 files
  }

  /** Tester for testType()
      @param arrays1 Input array
      @param arraysE Expected results.
      @param arrays2 Output arrays (same length as arraysE).
      @param len     Elements in each array of values.
   */
  private void checkType(Object[] arrays1, Object[] arraysE, Object[] arrays2, int len) {
    byte[] buf1 = new byte[len*8];
    byte[] buf2 = new byte[len*8];
    byte[] buf3 = new byte[len*8];

    for (int i = 0; i < arrays1.length; i++) {
      Object array1 = arrays1[i];
      byte   type1  = getType(array1);

      for (int j = 0; j < arrays2.length; j++) {
        Object arrayE = arraysE[j];
        Object array2 = arrays2[j];
        byte   type2  = getType(array2);
        int    bps1   = Data.getBPS(type1);
        int    bps2   = Data.getBPS(type2);
        String msg    = "type: Convert "+(char)type1+" to "+(char)type2;

        fillArray(buf1, (byte)0xAA);
        fillArray(buf2, (byte)0xAA);
        fillArray(buf3, (byte)0xAA);
        Convert.ja2bb(array1, 0, type1, buf1,   0, type1, len);
        Convert.ja2bb(array1, 0, type1, buf3,   0, type1, len);
        Convert.type (buf1,   0, type1, buf2,   0, type2, len);
        Convert.type (buf3,   0, type1, buf3,   0, type2, len);
        Convert.bb2ja(buf2,   0, type2, array2, 0, type2, len);

//double a1=0,a2=0,aE=0;
//for (int k = 0; k < bps2*len; k++) {
//  if (k%bps2 == 0) {
//    int index = k/bps2;
//    a1 = Array.getDouble(array1,index);
//    a2 = Array.getDouble(array2,index);
//    aE = Array.getDouble(arrayE,index);
//    System.out.println(msg+": array1["+index+"] = "+StringUtil.padRight(Double.toString(a1), 8)
//                           +" array2["+index+"] = "+StringUtil.padRight(Double.toString(a2), 8)
//                           +" arrayE["+index+"] = "+StringUtil.padRight(Double.toString(aE), 8)
//                           +" len="+len);
//  }
//  if (aE!=a2) System.out.println("buf2["+k+"]= "+buf2[k]+"   buf3["+k+"]= "+buf3[k]);
//}

        for (int k = 0; k < len; k++) {
          double exp = Array.getDouble(arrayE, k);
          double act = Array.getDouble(array2, k);
          double tol = 0.0;

          boolean toFloat   = (type2 == 'D') || (type2 == 'F');
          boolean fromFloat = (type1 == 'D') || (type1 == 'F');

               if (type1 == type2)        tol = 0.0;
          else if (toFloat)               tol = 0.001*Math.abs(exp);
          else if (fromFloat && !toFloat) tol = 0.001*Math.abs(exp);

          assertEquals(msg+": value at index "+k, exp, act, tol);
        }

        for (int k = 0; k < bps2*len; k++) {
          assertEquals(msg+" (convert in place): value at index "+k, buf2[k], buf3[k]);
        }
      }
    }
  }

  /** Fills the array with a given value. */
  private void fillArray(byte[] buf, byte val) {
    for (int i = 0; i < buf.length; i++) buf[i] = val;
  }

  private byte getType(Object array) {
    if (array instanceof double[]) return DataTypes.DOUBLE;
    if (array instanceof float[] ) return DataTypes.FLOAT;
    if (array instanceof long[]  ) return DataTypes.XLONG;
    if (array instanceof int[]   ) return DataTypes.LONG;
    if (array instanceof short[] ) return DataTypes.INT;
    if (array instanceof byte[]  ) return DataTypes.BYTE;
    throw new RuntimeException("Unknown type "+array.getClass());
  }


  public void testPackX() { } // Tested in testPackB()
  public void testPackL() { } // Tested in testPackB()
  public void testPackI() { } // Tested in testPackB()
  public void testPackD() { } // Tested in testPackB()
  public void testPackF() { } // Tested in testPackB()
  public void testPackN() { } // Tested in testPackB()

  public void testPackB() {
    byte[] xbufN = new byte[TEST_BUFF.length];
    byte[] xbufI = new byte[TEST_BUFF.length];
    byte[] xbufE = new byte[TEST_BUFF.length];
    byte[] lbufN = new byte[TEST_BUFF.length];
    byte[] lbufI = new byte[TEST_BUFF.length];
    byte[] lbufE = new byte[TEST_BUFF.length];
    byte[] ibufN = new byte[TEST_BUFF.length];
    byte[] ibufI = new byte[TEST_BUFF.length];
    byte[] ibufE = new byte[TEST_BUFF.length];
    byte[] dbufN = new byte[TEST_BUFF.length];
    byte[] dbufI = new byte[TEST_BUFF.length];
    byte[] dbufE = new byte[TEST_BUFF.length];
    byte[] fbufN = new byte[TEST_BUFF.length];
    byte[] fbufI = new byte[TEST_BUFF.length];
    byte[] fbufE = new byte[TEST_BUFF.length];
    byte[] bbuf  = new byte[TEST_BUFF.length];
    byte[] nbuf  = new byte[TEST_BUFF.length];

    for (int i = 0; i < XBUF.length; i++) { Convert.packX(xbufN, i*8, XBUF[i]           ); }
    for (int i = 0; i < XBUF.length; i++) { Convert.packX(xbufI, i*8, XBUF_IEEE[i], IEEE); }
    for (int i = 0; i < XBUF.length; i++) { Convert.packX(xbufE, i*8, XBUF_EEEI[i], EEEI); }
    for (int i = 0; i < LBUF.length; i++) { Convert.packL(lbufN, i*4, LBUF[i]           ); }
    for (int i = 0; i < LBUF.length; i++) { Convert.packL(lbufI, i*4, LBUF_IEEE[i], IEEE); }
    for (int i = 0; i < LBUF.length; i++) { Convert.packL(lbufE, i*4, LBUF_EEEI[i], EEEI); }
    for (int i = 0; i < IBUF.length; i++) { Convert.packI(ibufN, i*2, IBUF[i]           ); }
    for (int i = 0; i < IBUF.length; i++) { Convert.packI(ibufI, i*2, IBUF_IEEE[i], IEEE); }
    for (int i = 0; i < IBUF.length; i++) { Convert.packI(ibufE, i*2, IBUF_EEEI[i], EEEI); }
    for (int i = 0; i < DBUF.length; i++) { Convert.packD(dbufN, i*8, DBUF[i]           ); }
    for (int i = 0; i < DBUF.length; i++) { Convert.packD(dbufI, i*8, DBUF_IEEE[i], IEEE); }
    for (int i = 0; i < DBUF.length; i++) { Convert.packD(dbufE, i*8, DBUF_EEEI[i], EEEI); }
    for (int i = 0; i < FBUF.length; i++) { Convert.packF(fbufN, i*4, FBUF[i]           ); }
    for (int i = 0; i < FBUF.length; i++) { Convert.packF(fbufI, i*4, FBUF_IEEE[i], IEEE); }
    for (int i = 0; i < FBUF.length; i++) { Convert.packF(fbufE, i*4, FBUF_EEEI[i], EEEI); }
    for (int i = 0; i < BBUF.length; i++) { Convert.packB(bbuf, i, BBUF[i]); }
    for (int i = 0; i < NBUF.length; i++) { Convert.packN(nbuf, i, NBUF[i]); }

    assertArrayEquals("packX: Native Format", TEST_BUFF, xbufN);
    assertArrayEquals("packX: IEEE Format",   TEST_BUFF, xbufI);
    assertArrayEquals("packX: EEEI Format",   TEST_BUFF, xbufE);
    assertArrayEquals("packL: Native Format", TEST_BUFF, lbufN);
    assertArrayEquals("packL: IEEE Format",   TEST_BUFF, lbufI);
    assertArrayEquals("packL: EEEI Format",   TEST_BUFF, lbufE);
    assertArrayEquals("packI: Native Format", TEST_BUFF, ibufN);
    assertArrayEquals("packI: IEEE Format",   TEST_BUFF, ibufI);
    assertArrayEquals("packI: EEEI Format",   TEST_BUFF, ibufE);
    assertArrayEquals("packD: Native Format", TEST_BUFF, dbufN);
    assertArrayEquals("packD: IEEE Format",   TEST_BUFF, dbufI);
    assertArrayEquals("packD: EEEI Format",   TEST_BUFF, dbufE);
    assertArrayEquals("packF: Native Format", TEST_BUFF, fbufN);
    assertArrayEquals("packF: IEEE Format",   TEST_BUFF, fbufI);
    assertArrayEquals("packF: EEEI Format",   TEST_BUFF, fbufE);
    assertArrayEquals("packB", TEST_BUFF, bbuf);
    assertArrayEquals("packN", TEST_BUFF, nbuf);
  }


  public void testUnpackX() {
    for (int i = 0; i < XBUF.length; i++) {
      assertEquals("unpackX: Native Format i="+i, XBUF[i],      Convert.unpackX(TEST_BUFF,i*8));
      assertEquals("unpackX: IEEE Format i="+i,   XBUF_IEEE[i], Convert.unpackX(TEST_BUFF,i*8,IEEE));
      assertEquals("unpackX: EEEI Format i="+i,   XBUF_EEEI[i], Convert.unpackX(TEST_BUFF,i*8,EEEI));
    }
  }
  public void testUnpackL() {
    for (int i = 0; i < LBUF.length; i++) {
      assertEquals("unpackL: Native Format i="+i, LBUF[i],      Convert.unpackL(TEST_BUFF,i*4));
      assertEquals("unpackL: IEEE Format i="+i,   LBUF_IEEE[i], Convert.unpackL(TEST_BUFF,i*4,IEEE));
      assertEquals("unpackL: EEEI Format i="+i,   LBUF_EEEI[i], Convert.unpackL(TEST_BUFF,i*4,EEEI));
    }
  }
  public void testUnpackI() {
    for (int i = 0; i < IBUF.length; i++) {
      assertEquals("unpackI: Native Format i="+i, IBUF[i],      Convert.unpackI(TEST_BUFF,i*2));
      assertEquals("unpackI: IEEE Format i="+i,   IBUF_IEEE[i], Convert.unpackI(TEST_BUFF,i*2,IEEE));
      assertEquals("unpackI: EEEI Format i="+i,   IBUF_EEEI[i], Convert.unpackI(TEST_BUFF,i*2,EEEI));
    }
  }
  public void testUnpackB() {
    for (int i = 0; i < BBUF.length; i++) {
      assertEquals("unpackB: i="+i, BBUF[i], Convert.unpackB(TEST_BUFF,i));
    }
  }
  public void testUnpackN() {
    for (int i = 0; i < NBUF.length; i++) {
      assertEquals("unpackN: i="+i, NBUF[i], Convert.unpackN(TEST_BUFF,i));
    }
  }

  public void testUnpackD() {
    double[] EXPECTED_IEEE = { 0.15625, -118.625, 0, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY };
    byte[] buf = { (byte)0x3f, (byte)0xc4, (byte)0x00, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
                   (byte)0xc0, (byte)0x5d, (byte)0xa8, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
                   (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
                   (byte)0x7f, (byte)0xf8, (byte)0x00, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
                   (byte)0x7f, (byte)0xf0, (byte)0x00, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
                   (byte)0xff, (byte)0xf0, (byte)0x00, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0 };
    for (int i = 0; i < EXPECTED_IEEE.length; i++) {
      double result = Convert.unpackD(buf,i*8, IEEE);
      if (Double.isNaN(EXPECTED_IEEE[i])) assertTrue("unpackD: (NaN) i="+i, Double.isNaN(result));
      else assertEquals("unpackD: i="+i, EXPECTED_IEEE[i], result);
    }
    for (int i = 0; i < DBUF_IEEE.length; i++) {
      assertEquals("unpackD: Native Format i="+i, DBUF[i],      Convert.unpackD(TEST_BUFF,i*8));
      assertEquals("unpackD: IEEE Format i="+i,   DBUF_IEEE[i], Convert.unpackD(TEST_BUFF,i*8,IEEE));
      assertEquals("unpackD: EEEI Format i="+i,   DBUF_EEEI[i], Convert.unpackD(TEST_BUFF,i*8,EEEI));
    }
  }

  public void testUnpackF() {
    float[] EXPECTED_IEEE = { 0.15625F, -118.625F, 0F, Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY };
    byte[] buf = { (byte)0x3e, (byte)0x20, (byte)0x00, (byte)0x00,
                   (byte)0xc2, (byte)0xed, (byte)0x40, (byte)0x00,
                   (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                   (byte)0x7f, (byte)0xc0, (byte)0x00, (byte)0x00,
                   (byte)0x7f, (byte)0x80, (byte)0x00, (byte)0x00,
                   (byte)0xff, (byte)0x80, (byte)0x00, (byte)0x00 };
    for (int i = 0; i < EXPECTED_IEEE.length; i++) {
      float result = Convert.unpackF(buf,i*4, IEEE);
      if (Float.isNaN(EXPECTED_IEEE[i])) assertTrue("unpackF: (NaN) i="+i, Float.isNaN(result));
      else assertEquals("unpackF: i="+i, EXPECTED_IEEE[i], result);
    }
    for (int i = 0; i < FBUF_IEEE.length; i++) {
      assertEquals("unpackF: Native Format i="+i, FBUF[i],      Convert.unpackF(TEST_BUFF,i*4));
      assertEquals("unpackF: IEEE Format i="+i,   FBUF_IEEE[i], Convert.unpackF(TEST_BUFF,i*4,IEEE));
      assertEquals("unpackF: EEEI Format i="+i,   FBUF_EEEI[i], Convert.unpackF(TEST_BUFF,i*4,EEEI));
    }
  }

  public void testEncodeBase64() {
    final String str = "NeXtMidas unit test";
    final String expected = "TmVYdE1pZGFzIHVuaXQgdGVzdA==";
    assertEquals("encodeBase64", expected, Convert.encodeBase64(str));
  }

  public void testValidType() {
    char[] valid_type_list = DataTypes.CONVERT_TYPE_LIST.toCharArray();
    char[] invalid_type_list = { 'W', 'E', '1'};
    for (int i = 0; i < valid_type_list.length; i++) {
      char type = valid_type_list[i];
      assertTrue("validType: " + type, Convert.validType(type));
    }
    for (int i = 0; i < invalid_type_list.length; i++) {
      char type = invalid_type_list[i];
      assertFalse("validType: " + type, Convert.validType(type));
    }
  }
}
