package nxm.sys.test.junit.lib;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

/** This will test the Data class.

    @author  J. Czechowski IV / Neon Ngo / Conrad Clark
 */
public final class DataTest extends NMTestCase {

  /** public constructor to allow running this test case directly with JUnit. */
  public DataTest (String methodName) { this(methodName, null); }

  /** This constructor is required by NeXtMidas Unit Testing framework.
      @param methodName The name of the method being called.
      @param midas      The midas context to use when running the tests.
   */
  public DataTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  private static final char[]   TYPES       = { 'D', 'F', 'X', 'L', 'I', 'B' };
  private static final int[]    BPS         = {  8,   4,   8,   4,   2,   1  };
  private static final double[] DBPS        = {  8,   4,   8,   4,   2,   1  };
  private static final char[]   ALL_TYPES   = { 'D', 'F', 'X', 'L', 'I', 'B', 'N', 'P' };
  private static final int[]    ALL_BPE     = {  8,   4,   8,   4,   2,   1,  -4,  -1  };
  private static final double[] ALL_DBPE    = {  8,   4,   8,   4,   2,   1,  1/2, 1/8 };
  private static final char[]   MODES       = { 'S', 'C', 'V', 'Q',                     'M',     'T',
                                                '1', '2', '3', '4', '5', '6', '7', '8', '9', 'X' };
  private static final int[]    SPA         = {  1,   2,   3,   4,                       9,       16,
                                                 1,   2,   3,   4,   5,   6,   7,   8,   9,   10 };

  private static final String[] TYPE_NAMES  = { "Double", "Float", "XLongInt", "LongInt", "Integer", "Byte" };
  private static final String[] MODE_NAMES  = { "Scalar", "Complex", "Vector", "Quad", "Matrix", "TransMatrix",
                                                "One", "Two", "Three", "Four","Five","Six","Seven", "Eight", "Nine", "Ten" };

  // test data for equals() and hashCode() test cases
  private static final Data[][] TESTDATA_EQUALS_HASHCODE = {
    // first 3 items should be equal, 4th item should NOT be equal to previous 3
    {new Data(3.0),   new Data(3.0),   new Data(3.0),   new Data(2.0)},      // double
    {new Data(17),    new Data(17),    new Data(17),    new Data(22)},       // int
    {new Data(27),    new Data(27),    new Data(27),    new Data((byte)27)}, // int, byte for last item
    {new Data(37),    new Data(37),    new Data(37),    new Data(37f)},      // int, float for last item
    {new Data(47),    new Data(47),    new Data(47),    new Data(BPS)},      // int, int[] for last item
    {new Data(57),    new Data(57),    new Data(57),    new Data("57")},     // int, String for last item
    {new Data(DBPS),  new Data(DBPS),  new Data(DBPS),  new Data(ALL_DBPE)}, // double[]
    {new Data(SPA),   new Data(SPA),   new Data(SPA),   new Data(BPS)},      // int[]
    {new Data(SPA),   new Data(SPA),   new Data(SPA),   new Data(ALL_DBPE)}, // int[], double[] for last item
    {new Data("Hi"),  new Data("Hi"),  new Data("Hi"),  new Data("Hello")},  // String
    {new Data("Oh"),  new Data("Oh"),  new Data("Oh"),  new Data("oH")},     // String, different case String for last item
    {new Data("Nxm"), new Data("Nxm"), new Data("Nxm"), new Data((short)31)},// String, short for last item
  };


  // Used in a number of tests, especially testCastD
  private static final byte[]   INITIAL_B  = {  0,  1,  2,   3,  -1, -2, -3, 42, -42, 7 };  // initial values (x10)
  private static final byte[]   UPDATE_B   = { 27, 84, 66, 113, -19 };                      // update values (x5)
  private static final byte[]   FINAL_B    = { 27, 84, 66, 113, -19, -2, -3, 42, -42, 7 };  // final values (x10)
  private static final short[]  INITIAL_I  = new short[INITIAL_B.length];
  private static final short[]  FINAL_I    = new short[INITIAL_B.length];
  private static final int[]    INITIAL_L  = new int[INITIAL_B.length];
  private static final int[]    FINAL_L    = new int[INITIAL_B.length];
  private static final long[]   INITIAL_X  = new long[INITIAL_B.length];
  private static final long[]   FINAL_X    = new long[INITIAL_B.length];
  private static final float[]  INITIAL_F  = new float[INITIAL_B.length];
  private static final float[]  FINAL_F    = new float[INITIAL_B.length];
  private static final double[] INITIAL_D  = new double[INITIAL_B.length];
  private static final double[] FINAL_D    = new double[INITIAL_B.length];

  // These will be used to construct and/or test the contents of constructed Data objects
  private static final String   JA_STRING32 = "NeXtMidasDataUnitTestSampleArray"; // exactly 32 characters long
  private static final byte[]   BB_STRING32 = bufOf(JA_STRING32);
  private static final double[] JA_DOUBLEARRAY32 = buildDoubleArray(32);
  private static final byte[]   BB_DOUBLEARRAY32 = bufOf(JA_DOUBLEARRAY32);

  // These correspond to private methods of Data, which these unit tests must call via reflection.
  // To avoid collisions, the Method names are suffixed by the initials of their argument types.
  private static final Method method_Data_convertTo_iicc;
  private static final Method method_Data_fillBufferFrom_Di;
  private static final Method method_Data_fromString_SbbM;
  private static final Method method_Data_numBytes_i;

  private List<Long> longVals;
  private List<Integer> intVals;
  private List<Byte> byteVals;
  private List<Short> shortVals;
  private List<Float> floatVals;
  private List<Double> doubleVals;

  static {
    for (int i = 0; i < INITIAL_B.length; i++) {
      INITIAL_I[i] = INITIAL_B[i];      FINAL_I[i] = FINAL_B[i];
      INITIAL_L[i] = INITIAL_B[i];      FINAL_L[i] = FINAL_B[i];
      INITIAL_X[i] = INITIAL_B[i];      FINAL_X[i] = FINAL_B[i];
      INITIAL_F[i] = INITIAL_B[i];      FINAL_F[i] = FINAL_B[i];
      INITIAL_D[i] = INITIAL_B[i];      FINAL_D[i] = FINAL_B[i];
    }

    try {
      // prepare these Methods for use
      method_Data_convertTo_iicc = Data.class.getDeclaredMethod("convertTo", Integer.TYPE, Integer.TYPE, Character.TYPE, Character.TYPE);
      method_Data_convertTo_iicc.setAccessible(true);
      method_Data_fillBufferFrom_Di = Data.class.getDeclaredMethod("fillBufferFrom", Data.class, Integer.TYPE);
      method_Data_fillBufferFrom_Di.setAccessible(true);
      method_Data_fromString_SbbM = Data.class.getDeclaredMethod("fromString", String.class, Byte.TYPE, Byte.TYPE, MidasReference.class);
      method_Data_fromString_SbbM.setAccessible(true);
      method_Data_numBytes_i = Data.class.getDeclaredMethod("numBytes", Integer.TYPE);
      method_Data_numBytes_i.setAccessible(true);
    } catch (Exception e) {
      throw new RuntimeException("DataTest initialization failed", e);
    }
  }

  // use reflection to call package-private method Data.convertTo(int,int,char,char)
  private Data Data_convertTo (Data thisData, int newSize, int newSPA, char newMode, char newType) {
    try {
      return (Data)method_Data_convertTo_iicc.invoke(thisData, newSize, newSPA, newMode, newType);
    } catch (InvocationTargetException e) { // unwrap any wrapped RuntimeExceptions
      if (e.getCause() instanceof RuntimeException) throw (RuntimeException)e.getCause();
      else throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  // use reflection to call private method Data.fillBufferFrom(Data,int)
  private void Data_fillBufferFrom (Data thisData, Data src, int numElements) {
    try {
      method_Data_fillBufferFrom_Di.invoke(thisData, src, numElements);
    } catch (InvocationTargetException e) { // unwrap any wrapped RuntimeExceptions
      if (e.getCause() instanceof RuntimeException) throw (RuntimeException)e.getCause();
      else throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  // use reflection to call private static method Data.fromString(String,byte,byte,MidasReference)
  private Data Data_fromString (String string, byte newMode, byte newType, MidasReference ref) {
    try {
      return (Data)method_Data_fromString_SbbM.invoke(null, string, newMode, newType, ref);
    } catch (InvocationTargetException e) { // unwrap any wrapped RuntimeExceptions
      if (e.getCause() instanceof RuntimeException) throw (RuntimeException)e.getCause();
      else throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  // use reflection to call private method Data.numBytes(int)
  private int Data_numBytes (Data thisData, int numElements) {
    try {
      return (int)method_Data_numBytes_i.invoke(thisData, numElements);
    } catch (InvocationTargetException e) { // unwrap any wrapped RuntimeExceptions
      if (e.getCause() instanceof RuntimeException) throw (RuntimeException)e.getCause();
      else throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  // independent copy of Data.spaFromMode(byte)
  @SuppressWarnings("fallthrough")
  private static int spaFromMode (char mode) {
    switch (mode) {
      case 'S':
      case 'N':            // special case for 'NH' format
      case 'K':            // special case for 'KW' format
      case '1': return 1;
      case 'C':
      case '2': return 2;
      case 'V':
      case '3': return 3;
      case 'Q':
      case '4': return 4;
      case '5': return 5;
      case '6': return 6;
      case '7': return 7;
      case '8': return 8;
      case 'M':
      case '9': return 9;
      case 'X': return 10;
      case 'T': return 16;
      case 'A': return 32; // since 3.1.0 to match X-Midas 4.6.0+
    }
    throw new IllegalArgumentException("DataTest: '"+mode+"' is not a valid MODE");
  }

  // independent copy of Data.bpsFromType(byte)
  @SuppressWarnings("fallthrough")
  private static int bpsFromType (char type) {
    switch (type) {
      case DataTypes.ASCII:
      case DataTypes.DOUBLE:
      case DataTypes.XLONG:    return  8; // bytes
      case DataTypes.FLOAT:
      case DataTypes.LONG:     return  4; // bytes
      case DataTypes.INT:      return  2; // bytes
      case DataTypes.JINT:     return  -12; // bytes NXM-3894 Jeff 12-bit J
      case DataTypes.BYTE:
      case 'H':            // special case for 'NH' format
      case 'W':            // special case for 'KW' format
      case 'O':            // special case for XMValue.INT_U1
      case 'M': return  1; // byte
      case DataTypes.NIBBLE:   return -4; // bits
      case DataTypes.PACKED:   return -1; // bit
      case DataTypes.ASCII256: return 256; // NXM-3570
    }
    throw new IllegalArgumentException("DataTest: '"+type+"' is not a valid TYPE");
  }

  // does exactly what a Data constructor would have to do to create the Data.buf field from a String
  private static byte[] bufOf (String s) {
    byte[] buf = new byte[1*s.length()];
    Convert.packS(buf, 0, s.length(), s);
    return buf;
  }

  // does exactly what a Data constructor would have to do to create the Data.buf field from a double[]
  private static byte[] bufOf (double[] da) {
    byte[] buf = new byte[8*da.length];
    Convert.ja2bb(da,0,(byte)'D', buf,0,(byte)'D', da.length);
    return buf;
  }

  /** Generates sine/cosine pairs around a circle, which can be meaningfully interpreted as being of format "CD".
      If size==32, this generates data corresponding to a complete circle without overlap.
   */
  private static double[] buildDoubleArray (int size) {
    double[] da = new double[size];
    double deg=0.0;
    for (int i=0; i<size; deg+=11.25) { // 11.25*32=360
      da[i++] = Math.sin(Math.toRadians(deg));
      da[i++] = Math.cos(Math.toRadians(deg));
    }
    for (int i=0; i<size; ++i) if (-17>Math.getExponent(da[i])) da[i] = 0.0; // round vanishingly small values to zero
    return da;
  }

  //////////////////////////////////////////////////////////////////////////////
  public void testDump()          { } // no applicable test case

  //////////////////////////////////////////////////////////////////////////////
  public void testConstructor() {     // test we get error from unsupported types
    int     eCount = 0;
    Table   t1     = new Table();
    Boolean b1     = Boolean.FALSE;
    Data    data;
    try { data = new Data(t1); } catch (MidasException mm) { eCount++; }
    try { data = new Data(b1); } catch (MidasException mm) { eCount++; }
    try { data = new Data(this); } catch (MidasException mm) { eCount++; }
    assertEquals("Data constructor - invalid inputs", eCount, 3);
    // the below constructors should not throw any exceptions
    data = new Data(1.0);      // double constructor
    assertEquals("new Data(double)", 1.0, data.getNumber(0));
    data = new Data(2.0f);     // float constructor
    assertEquals("new Data(float)", 2.0f, data.getNumber(0));
    data = new Data(3L);       // long constructor
    assertEquals("new Data(long)", 3L, data.getNumber(0));
    data = new Data(4);        // int constructor
    assertEquals("new Data(int)", 4, data.getNumber(0));
    data = new Data((short)5); // short constructor
    assertEquals("new Data(short)", (short)5, data.getNumber(0));
    data = new Data((byte)6);  // byte constructor
    assertEquals("new Data(byte)", (byte)6, data.getNumber(0));

    // Test the constructors with default values set
    _testConstructor("SN", 1024, (byte)0, (byte)7);
    _testConstructor("CN", 1024, (byte)0, (byte)7);
    _testConstructor("SB", 1024, (byte)0, (byte)42);
    _testConstructor("CB", 1024, (byte)0, (byte)42);
    _testConstructor("SI", 1024, (short)0, (short)42);
    _testConstructor("CI", 1024, (short)0, (short)42);
    _testConstructor("SL", 1024, 0,    42);
    _testConstructor("CL", 1024, 0,    42);
    _testConstructor("SX", 1024, 0L,   42L);
    _testConstructor("CX", 1024, 0L,   42L);
    _testConstructor("SF", 1024, 0.0f, 3.14f);
    _testConstructor("CF", 1024, 0.0f, 3.14f);
    _testConstructor("SD", 1024, 0.0d, 3.14d);
    _testConstructor("CD", 1024, 0.0d, 3.14d);

    // Test the varargs form of the constructors
// TODO: Restore these under Java 7 (Java 6 incorrectly gives an "ambiguous definition" error when compiling).
//       See Java Bug ID: 6199075 Unambiguous varargs method calls flagged as ambiguous
//       http://bugs.sun.com/view_bug.do?bug_id=6199075
    data = new Data((byte)0, (byte)1, (byte)2, (byte)3);
//    data = new Data((byte)0);
    assertEquals("new Data(byte ... value)", (byte)0, data.getNumber(0));
    assertEquals("new Data(byte ... value)", (byte)1, data.getNumber(1));
    assertEquals("new Data(byte ... value)", (byte)2, data.getNumber(2));
    assertEquals("new Data(byte ... value)", (byte)3, data.getNumber(3));

     data = new Data((short)0, (short)1, (short)2, (short)3);
     assertEquals("new Data(short ... value)", (short)0, data.getNumber(0));
     assertEquals("new Data(short ... value)", (short)1, data.getNumber(1));
     assertEquals("new Data(short ... value)", (short)2, data.getNumber(2));
     assertEquals("new Data(short ... value)", (short)3, data.getNumber(3));

     data = new Data(0, 1, 2, 3);
     assertEquals("new Data(int ... value)", 0, data.getNumber(0));
     assertEquals("new Data(int ... value)", 1, data.getNumber(1));
     assertEquals("new Data(int ... value)", 2, data.getNumber(2));
     assertEquals("new Data(int ... value)", 3, data.getNumber(3));

     data = new Data(0L, 1L, 2L, 3L);
     assertEquals("new Data(long ... value)", 0L, data.getNumber(0));
     assertEquals("new Data(long ... value)", 1L, data.getNumber(1));
     assertEquals("new Data(long ... value)", 2L, data.getNumber(2));
     assertEquals("new Data(long ... value)", 3L, data.getNumber(3));

     data = new Data(0f, 1f, 2f, 3f);
     assertEquals("new Data(float ... value)", 0f, data.getNumber(0));
     assertEquals("new Data(float ... value)", 1f, data.getNumber(1));
     assertEquals("new Data(float ... value)", 2f, data.getNumber(2));
     assertEquals("new Data(float ... value)", 3f, data.getNumber(3));

     data = new Data(0d, 1d, 2d, 3d);
     assertEquals("new Data(double ... value)", 0d, data.getNumber(0));
     assertEquals("new Data(double ... value)", 1d, data.getNumber(1));
     assertEquals("new Data(double ... value)", 2d, data.getNumber(2));
     assertEquals("new Data(double ... value)", 3d, data.getNumber(3));

     arrayListConstructor();

  }

  private void _testConstructor (String form, int size, Number ZERO, Number defVal) {
    Data   none = new Data(form, size);
    Data   zero = new Data(form, size, ZERO);
    Data   data = new Data(form, size, defVal);

    for (int i = 0; i < size*data.spa; i++) {
      assertEquals("new Data("+form+","+size           +") @ "+i, ZERO,   none.getNumber(i));
      assertEquals("new Data("+form+","+size+","+ZERO  +") @ "+i, ZERO,   zero.getNumber(i));
      assertEquals("new Data("+form+","+size+","+defVal+") @ "+i, defVal, data.getNumber(i));
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The from?() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testFromString () {
    _testFromString_withByteArray();
  }

  private void _testFromString_withByteArray () {
    _testFromString_withByteArray("SCO",  1, 'S', 'B');
    _testFromString_withByteArray("SCO",  2, 'C', 'B');
    _testFromString_withByteArray("SCO",  3, 'V', 'B');
    _testFromString_withByteArray("SCO",  4, 'Q', 'B');
    _testFromString_withByteArray("SCO",  5, 'S', 'B');
    _testFromString_withByteArray("SCO",  9, 'S', 'B');
    _testFromString_withByteArray("SCO", 10, 'S', 'B');
    _testFromString_withByteArray("SCO", 16, 'S', 'B');
    _testFromString_withByteArray("SCO", 32, 'S', 'B');

    _testFromString_withByteArray("SBBM", 1, 'S', 'B');
    _testFromString_withByteArray("SBBM", 2, 'S', 'B');
    _testFromString_withByteArray("SBBM", 2, 'C', 'B');
    _testFromString_withByteArray("SBBM", 3, 'S', 'B');
    _testFromString_withByteArray("SBBM", 3, 'V', 'B');
    _testFromString_withByteArray("SBBM", 4, 'S', 'B');
    _testFromString_withByteArray("SBBM", 4, 'C', 'B');
    _testFromString_withByteArray("SBBM", 4, 'Q', 'B');
    _testFromString_withByteArray("SBBM", 5, 'S', 'B');
    _testFromString_withByteArray("SBBM", 5, '5', 'B');
    _testFromString_withByteArray("SBBM", 6, 'S', 'B');
    _testFromString_withByteArray("SBBM", 6, 'C', 'B');
    _testFromString_withByteArray("SBBM", 6, 'V', 'B');
    _testFromString_withByteArray("SBBM", 6, '6', 'B');
    _testFromString_withByteArray("SBBM", 7, 'S', 'B');
    _testFromString_withByteArray("SBBM", 7, '7', 'B');
    _testFromString_withByteArray("SBBM", 8, 'S', 'B');
    _testFromString_withByteArray("SBBM", 8, 'C', 'B');
    _testFromString_withByteArray("SBBM", 8, 'Q', 'B');
    _testFromString_withByteArray("SBBM", 8, '8', 'B');
    _testFromString_withByteArray("SBBM", 9, 'S', 'B');
    _testFromString_withByteArray("SBBM", 9, 'V', 'B');
    _testFromString_withByteArray("SBBM", 9, 'M', 'B');
  }

  private void _testFromString_withByteArray (String methodSig, int numScalars, char cMode, char cType) {
    byte mode = (byte)cMode;
    byte type = (byte)cType;
    int bps = bpsFromType(cType);
    int spa = spaFromMode(cMode);
    int ape = 1;
    int bpe = bps * spa * ape;
    int numElements = numScalars / (spa * ape);

    StringBuilder sb = new StringBuilder();
    sb.append('(');
    int i=0;
    for (byte b : BB_STRING32) {
      if (++i>numScalars) {
        break;
      }
      sb.append(b).append(",");
    }
    if (numScalars>0) {
      sb.setLength(sb.length()-1); // remove the trailing comma
    }
    String al = sb.append(')').toString();

    String cs = "Data.fromString(\""+al+"\", '"+cType+"', null)";
    Data data = null;
    if ("SCO".equals(methodSig)) {
      data = Data.fromString(al, cType, null);
    }
    else if ("SBBM".equals(methodSig)) {
      data = Data_fromString(al, mode, type, null);
    }
    else {
      fail("invalid methodSig: \""+methodSig+"\"");
    }

    assertArrayEquals(cs+".buf", Arrays.copyOf(BB_STRING32, numScalars), data.buf);
    assertEquals(cs+".mode", mode, data.mode);
    assertEquals(cs+".type", type, data.type);
    assertEquals(cs+".bps", bps, data.bps);
    assertEquals(cs+".spa", spa, data.spa);
    assertEquals(cs+".ape", ape, data.ape);
    assertEquals(cs+".bpe", bpe, data.bpe);
    assertEquals(cs+".size", numElements, data.size);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The get?() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testGetFormatMode() { } // tested in testGetFormat()
  public void testGetFormatType() { } // tested in testGetFormat()
  public void testGetFormatModeChar() { } // tested in testGetFormat()
  public void testGetFormatTypeChar() { } // tested in testGetFormat()
  public void testGetTypeLetter() { } // DEPRECATED: tested in testGetFormat()

  @SuppressWarnings(value={"deprecation"}) // to ignore Data.getTypeLetter (since we are testing deprecated functionality)
  public void testGetFormat() {
    for (int i = 0; i < TYPES.length; i++) {
      for (int j = 0; j < MODES.length; j++) {
        byte   type = (byte)TYPES[i];
        byte   mode = (byte)MODES[j];
        String form = ""+MODES[j]+TYPES[i];
        Data   data = new Data(form,1);

        assertEquals("Data.getFormat() for "+form, form, data.getFormat());
        assertEquals("Data.getFormatMode() for "+mode,   mode,     data.getFormatMode());
        assertEquals("Data.getFormatType() for "+type,   type,     data.getFormatType());
        assertEquals("Data.getTypeLetter() for "+mode,   TYPES[i], data.getTypeLetter()); // <-- test deprecated method
      }
    }
  }

  public void testGetBPS()        { } // tested in testGetBPA()
  public void testGetSPA()        { } // tested in testGetBPA()

  public void testGetBPA() {
    for (int i = 0; i < TYPES.length; i++) {
      for (int j = 0; j < MODES.length; j++) {
        String form  = ""+MODES[j]+TYPES[i];
        Data   data1 = new Data(form,1);
        Data   data2 = new Data(form,2);

        int bps = BPS[i];
        int spa = SPA[j];
        int bpa = bps * spa;

        assertEquals("Data.getBPA(String) for "+form, bpa,  Data.getBPA(form));
        assertEquals("Data.getBPS(char) for "+form, BPS[i], Data.getBPS(      TYPES[i]));
        assertEquals("Data.getBPS(byte) for "+form, BPS[i], Data.getBPS((byte)TYPES[i]));
        assertEquals("Data.getSPA(char) for "+form, SPA[i], Data.getSPA(      MODES[i]));
        assertEquals("Data.getSPA(byte) for "+form, SPA[i], Data.getSPA((byte)MODES[i]));
        assertEquals("Data.getSPA() for "+form+" size=1", spa,  data1.getSPA());
        assertEquals("Data.getSPA() for "+form+" size=2", spa,  data2.getSPA());
      }
    }
  }

  public void testGetFormatTypeName() {
    for (int i = 0; i < TYPES.length; i++) {
      assertEquals("Data.getFormatTypeName(..) for "+TYPES[i], TYPE_NAMES[i], Data.getFormatTypeName((byte)TYPES[i]));
    }
    assertEquals("Data.getFormatTypeName(..) for "+'A', "Ascii", Data.getFormatTypeName((byte)'A'));
  }

  public void testGetFormatModeName() {
    for (int i = 0; i < MODES.length; i++) {
      assertEquals("Data.getFormatModeName(..) for "+MODES[i], MODE_NAMES[i], Data.getFormatModeName((byte)MODES[i]));
    }
  }

  public void testGetRepName() {
    assertEquals("Data.getRepName('V')", "VAX",  Data.getRepName((byte)'V'));
    assertEquals("Data.getRepName('I')", "IEEE", Data.getRepName((byte)'I'));
    assertEquals("Data.getRepName('E')", "EEEI", Data.getRepName((byte)'E'));
    assertEquals("Data.getRepName('C')", "CRAY", Data.getRepName((byte)'C'));
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The set?() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testSetType () { } // deprecated (tested in testSetFormatType)

  public void testSetFormatMode() { } // tested in testSetFormatType()

  public void testSetFormatType () {
    for (char ctype : TYPES) {
      for (char cmode : MODES) {
        byte   type = (byte)ctype;
        byte   mode = (byte)cmode;

        Data   data  = new Data();
        data.setFormatType(type);
        data.setFormatMode(mode);
        assertEquals("Data.set/getFormatMode() for "+cmode, mode, data.getFormatMode());
        assertEquals("Data.set/getFormatType() for "+ctype, type, data.getFormatType());

        // test new methods added in NeXtMidas 3.3.0
        Data   data2  = new Data();
        data2.setFormatMode(cmode);
        data2.setFormatType(ctype);
        assertEquals("Data.set/getFormatModeChar()", cmode, data.getFormatModeChar());
        assertEquals("Data.set/getFormatTypeChar()", ctype, data.getFormatTypeChar());

        // BEGIN: test deprecated setType and setMode
        Data   data3 = new Data();
        data3.setType(type);
        data3.setMode(mode);
        assertEquals("Data.setType/getFormatMode() for "+cmode, mode, data3.getFormatMode());
        assertEquals("Data.setMode/getFormatType() for "+ctype, type, data3.getFormatType());
        // END: testing of deprecated methods
      }
    }
  }

  public void getElementAt     () { } // tested in testSetScalarAt
  public void testSetAPE       () { } // tested in testSetScalarAt
  public void testSetElementAt () { } // tested in testSetScalarAt
  public void testSetAtomAt    () { } // tested in testSetScalarAt

  public void testSetScalarAt () {
    String format = "CF";
    int size = 6; // NOTE: If BUG 2433 is not resolved, keep size even or change format to CD
    float[] array = {42, 789, 234, 69};
    float[] check;
    Data dat1 = new Data(format, size);
    Data fiveData = new Data((float)5.0);
    Data dataArray = new Data(array);

    // Test setScalarAt
    dat1.setScalarAt(4, array);    //byte offset
    dat1.setScalarAt(12, fiveData);
    check = dat1.castF(true);
    float[] expected = {0, array[0], 0, 5, 0, 0, 0, 0, 0, 0, 0, 0};
    assertEquals("Checking setScalarAt - size/ape/spa", dat1.size*dat1.spa, check.length);
    assertArrayEquals("Checking setScalarAt - expected output from castF", expected, check);
    dat1.uncast(check, false);

    // Test setAtomAt
    dat1.setAtomAt(1, dataArray); //offset = # of atoms
    check = dat1.castF(true);
    float[] expected2 = {0, array[0], /* change begins here */
                                      array[0], array[1], 0, 0, 0, 0, 0, 0, 0, 0};
    assertEquals("Checking setAtomAt - size/ape/spa", dat1.size*dat1.spa,    check.length);
    assertArrayEquals("Checking setAtomAt - expected output from castF", expected2, check);
    dat1.uncast(check, false);

    // Test setElementAt
    dat1.setAPE(2);
    int expectedSize = (int)Math.ceil(size/2.0);
    assertEquals(expectedSize, dat1.size);
    dat1.setElementAt(1, dataArray); //offset = # of elements
    check = dat1.castF(true);
    float[] expected3 = {0, array[0], array[0], array[1], /* change begins here */
                                                          array[0], array[1], array[2], array[3], 0, 0, 0, 0};
    assertEquals("Checking setElementAt - size/ape/spa", dat1.size*dat1.ape*dat1.spa, check.length);
    assertArrayEquals("Checking setElementAt - expected output from castF", expected3, check);
    dat1.uncast(check, false);

    // Test getElementAt
    Data el1 = dat1.getElementAt(dat1.bpe*1, format, 1);
    check = el1.castF(true);
    float[] expected4 = {array[0], array[1], array[2], array[3]};
    assertArrayEquals("Checking getElementAt - expected output from castF", expected4, check);
    dat1.uncast(check, false);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The to?() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testToD() {
    assertEquals("Data.toD() of '3.14' (type=A)", 3.14, (new Data("3.14")).toD());

    // needs more tests
  }

  public void testConvertTo () {
    // Test convertTo(char)
    // TODO

    // Test convertTo(char,int,boolean)
    // TODO

    // Test convertTo(int,int,char,char), convertTo(int,int,byte,byte), convertTo(byte,byte,int), fillBufferFrom(Data,int)

    // Construct a SCALAR-ASCII object to be used in subsequent unit tests.
    Data dataInSA = new Data(JA_STRING32);
    assertEquals("dataInSA.getFormat()", "SA", dataInSA.getFormat());
    assertEquals("dataInSA.getSize()", 4, dataInSA.getSize());

    // Construct a SCALAR-BYTE object to be used in subsequent unit tests.
    Data dataInSB = new Data(BB_STRING32, 0, BB_STRING32.length, 'B', 1);
    assertEquals("dataInSB.getFormat()", "SB", dataInSB.getFormat());
    assertEquals("dataInSB.getSize()", 32, dataInSB.getSize());

    // Construct a COMPLEX-DOUBLE object to be used in subsequent unit tests.
    Data dataInCD = Data_convertTo(new Data(JA_DOUBLEARRAY32), -1, -1, 'C', '_');
    assertEquals("dataInCD.getFormat()", "CD", dataInCD.getFormat());
    assertEquals("dataInCD.getSize()", 16, dataInCD.getSize());

    // Trying all of the digits and all of the uppercase letters as inputs for mode and type,
    // attempt to convert the given Data object.
    bulkTestOf_convertTo(dataInSA, "A", "0123456789BCDEFGHIJKLMNOPQRSTUVWXYZ");
    bulkTestOf_convertTo(dataInSB, "BDFIJLNPX", "0123456789ACEGHKMOQRSTUVWYZ");
    bulkTestOf_convertTo(dataInCD, "BDFIJLNPX", "0123456789ACEGHKMOQRSTUVWYZ");

    // Verify that convertTo() correctly interprets and accepts these valid inputs.
    _testOf_convertTo(dataInSB, -1, -1, ' ', '_', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1, -1, ' ', 'B', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1,  1, ' ', '_', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1,  1, 'S', '_', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1,  1, '1', '_', 32,  1, "1B", null);
    _testOf_convertTo(dataInSB, -1, -1, '1', '_', 32,  1, "1B", null);
    _testOf_convertTo(dataInSB, -1, -1, 'S', '_', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'S', 'B', 32,  1, "SB", null);
    _testOf_convertTo(dataInSB, 31, -1, 'S', 'B', 31,  1, "SB", null);
    _testOf_convertTo(dataInSB, 33, -1, 'S', 'B', 33,  1, "SB", null);
    _testOf_convertTo(dataInSB, -1,  2, ' ', '_', 16,  2, "CB", null);
    _testOf_convertTo(dataInSB, -1,  2, 'C', '_', 16,  2, "CB", null);
    _testOf_convertTo(dataInSB, -1,  2, '2', '_', 16,  2, "2B", null);
    _testOf_convertTo(dataInSB, -1, -1, '2', '_', 16,  2, "2B", null);
    _testOf_convertTo(dataInSB, -1, -1, 'C', '_', 16,  2, "CB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'C', 'B', 16,  2, "CB", null);
    _testOf_convertTo(dataInSB, 15, -1, 'C', 'B', 15,  2, "CB", null);
    _testOf_convertTo(dataInSB, 17, -1, 'C', 'B', 17,  2, "CB", null);
    _testOf_convertTo(dataInSB, -1,  3, ' ', '_', 11,  3, "VB", null);
    _testOf_convertTo(dataInSB, -1,  3, 'V', '_', 11,  3, "VB", null);
    _testOf_convertTo(dataInSB, -1,  3, '3', '_', 11,  3, "3B", null);
    _testOf_convertTo(dataInSB, -1, -1, '3', '_', 11,  3, "3B", null);
    _testOf_convertTo(dataInSB, -1, -1, 'V', '_', 11,  3, "VB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'V', 'B', 11,  3, "VB", null);
    _testOf_convertTo(dataInSB, 10, -1, 'V', 'B', 10,  3, "VB", null);
    _testOf_convertTo(dataInSB, 12, -1, 'V', 'B', 12,  3, "VB", null);
    _testOf_convertTo(dataInSB, -1,  4, ' ', '_',  8,  4, "QB", null);
    _testOf_convertTo(dataInSB, -1,  4, 'Q', '_',  8,  4, "QB", null);
    _testOf_convertTo(dataInSB, -1,  4, '4', '_',  8,  4, "4B", null);
    _testOf_convertTo(dataInSB, -1, -1, '4', '_',  8,  4, "4B", null);
    _testOf_convertTo(dataInSB, -1, -1, 'Q', '_',  8,  4, "QB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'Q', 'B',  8,  4, "QB", null);
    _testOf_convertTo(dataInSB,  7, -1, 'Q', 'B',  7,  4, "QB", null);
    _testOf_convertTo(dataInSB,  9, -1, 'Q', 'B',  9,  4, "QB", null);
    _testOf_convertTo(dataInSB, -1,  5, ' ', '_',  7,  5, "5B", null);
    _testOf_convertTo(dataInSB, -1,  5, '5', '_',  7,  5, "5B", null);
    _testOf_convertTo(dataInSB, -1, -1, '5', '_',  7,  5, "5B", null);
    _testOf_convertTo(dataInSB, -1, -1, '5', 'B',  7,  5, "5B", null);
    _testOf_convertTo(dataInSB,  6, -1, '5', 'B',  6,  5, "5B", null);
    _testOf_convertTo(dataInSB,  8, -1, '5', 'B',  8,  5, "5B", null);
    _testOf_convertTo(dataInSB, -1,  6, ' ', '_',  6,  6, "6B", null);
    _testOf_convertTo(dataInSB, -1,  6, '6', '_',  6,  6, "6B", null);
    _testOf_convertTo(dataInSB, -1, -1, '6', '_',  6,  6, "6B", null);
    _testOf_convertTo(dataInSB, -1, -1, '6', 'B',  6,  6, "6B", null);
    _testOf_convertTo(dataInSB,  5, -1, '6', 'B',  5,  6, "6B", null);
    _testOf_convertTo(dataInSB,  7, -1, '6', 'B',  7,  6, "6B", null);
    _testOf_convertTo(dataInSB, -1,  7, ' ', '_',  5,  7, "7B", null);
    _testOf_convertTo(dataInSB, -1,  7, '7', '_',  5,  7, "7B", null);
    _testOf_convertTo(dataInSB, -1, -1, '7', '_',  5,  7, "7B", null);
    _testOf_convertTo(dataInSB, -1, -1, '7', 'B',  5,  7, "7B", null);
    _testOf_convertTo(dataInSB,  4, -1, '7', 'B',  4,  7, "7B", null);
    _testOf_convertTo(dataInSB,  6, -1, '7', 'B',  6,  7, "7B", null);
    _testOf_convertTo(dataInSB, -1,  8, ' ', '_',  4,  8, "8B", null);
    _testOf_convertTo(dataInSB, -1,  8, '8', '_',  4,  8, "8B", null);
    _testOf_convertTo(dataInSB, -1, -1, '8', '_',  4,  8, "8B", null);
    _testOf_convertTo(dataInSB, -1, -1, '8', 'B',  4,  8, "8B", null);
    _testOf_convertTo(dataInSB,  3, -1, '8', 'B',  3,  8, "8B", null);
    _testOf_convertTo(dataInSB,  5, -1, '8', 'B',  5,  8, "8B", null);
    _testOf_convertTo(dataInSB, -1,  9, ' ', '_',  4,  9, "MB", null);
    _testOf_convertTo(dataInSB, -1,  9, 'M', '_',  4,  9, "MB", null);
    _testOf_convertTo(dataInSB, -1,  9, '9', '_',  4,  9, "9B", null);
    _testOf_convertTo(dataInSB, -1, -1, '9', '_',  4,  9, "9B", null);
    _testOf_convertTo(dataInSB, -1, -1, 'M', '_',  4,  9, "MB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'M', 'B',  4,  9, "MB", null);
    _testOf_convertTo(dataInSB,  3, -1, 'M', 'B',  3,  9, "MB", null);
    _testOf_convertTo(dataInSB,  5, -1, 'M', 'B',  5,  9, "MB", null);
    _testOf_convertTo(dataInSB, -1, 10, ' ', '_',  4, 10, "XB", null);
    _testOf_convertTo(dataInSB, -1, 10, 'X', '_',  4, 10, "XB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'X', '_',  4, 10, "XB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'X', 'B',  4, 10, "XB", null);
    _testOf_convertTo(dataInSB,  3, -1, 'X', 'B',  3, 10, "XB", null);
    _testOf_convertTo(dataInSB,  5, -1, 'X', 'B',  5, 10, "XB", null);
    _testOf_convertTo(dataInSB, -1, 16, ' ', '_',  2, 16, "TB", null);
    _testOf_convertTo(dataInSB, -1, 16, 'T', '_',  2, 16, "TB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'T', '_',  2, 16, "TB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'T', 'B',  2, 16, "TB", null);
    _testOf_convertTo(dataInSB,  1, -1, 'T', 'B',  1, 16, "TB", null);
    _testOf_convertTo(dataInSB,  3, -1, 'T', 'B',  3, 16, "TB", null);
    _testOf_convertTo(dataInSB, -1, 32, ' ', '_',  1, 32, "AB", null);
    _testOf_convertTo(dataInSB, -1, 32, 'A', '_',  1, 32, "AB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'A', '_',  1, 32, "AB", null);
    _testOf_convertTo(dataInSB, -1, -1, 'A', 'B',  1, 32, "AB", null);
    _testOf_convertTo(dataInSB,  0, -1, 'A', 'B',  0, 32, "AB", null);
    _testOf_convertTo(dataInSB,  2, -1, 'A', 'B',  2, 32, "AB", null);

    // Verify that convertTo() correctly rejects these contradictory or otherwise invalid inputs.
    _testOf_convertTo(dataInSB, -2, -1, 'S', 'B',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, -2, 'S', 'B',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  0, '1', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  0, 'S', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  2, '1', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  2, 'S', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  1, '2', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  1, 'C', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  3, '2', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  3, 'C', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  2, '3', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  2, 'V', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  4, '3', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  4, 'V', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  3, '4', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  3, 'Q', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  5, '4', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  5, 'Q', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  4, '5', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  6, '5', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  5, '6', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  7, '6', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  6, '7', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  8, '7', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  7, '8', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  9, '8', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  8, '9', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 10, '9', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  8, 'M', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 10, 'M', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1,  9, 'X', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 11, 'X', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 15, 'T', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 17, 'T', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 31, 'A', '_',  0,  0, null, IllegalArgumentException.class);
    _testOf_convertTo(dataInSB, -1, 33, 'A', '_',  0,  0, null, IllegalArgumentException.class);
  }


  /** Trying all of the digits and all of the uppercase letters as inputs for mode,
      attempt to convert the given Data object using each of the given valid and invalid
      characters for type.
      @param dataIn       A Data object to be used as a source of data for conversion.
      @param validTypes   A String of zero or more characters, each of which is expected to be
                          accepted as a valid input for type.
      @param invalidTypes A String of zero or more characters, each of which is expected to be
                          rejected as an invalid input for type.
   */
  private void bulkTestOf_convertTo (Data dataIn, String validTypes, String invalidTypes) {
    int speIn = dataIn.getSPA() * dataIn.getAPE();
    int numElementsIn = dataIn.getSize();
    int numScalarsIn = speIn * numElementsIn;

    for (char mode : "123456789ACMQSTVX".toCharArray()) {
      int expectedSPA = spaFromMode(mode); // ape will be 1
      int expectedSize = numScalarsIn/expectedSPA; if (numScalarsIn%expectedSPA>0) ++expectedSize; // round up if it doesn't divide evenly

      for (char type : validTypes.toCharArray()) {
        _testOf_convertTo(dataIn, -1, -1, mode, type, expectedSize, expectedSPA, mode+""+type, null);
      }
      for (char type : invalidTypes.toCharArray()) {
        _testOf_convertTo(dataIn, -1, -1, mode, type, expectedSize, expectedSPA, mode+""+type, IllegalArgumentException.class);
      }
    }
    for (char mode : "0BDEFGHIKLNOPRUWY".toCharArray()) {
      _testOf_convertTo(dataIn, -1, -1, mode, (char)dataIn.getFormatType(), 0, 0, null, IllegalArgumentException.class);
    }
  }

  /** Call {@link Data#convertTo(int, int, char, char)} and verify the result.  If no exception is thrown,
      then use the resulting Data object as the basis for populating new Data objects via calls to
      {@link Data#fillBufferFrom(Data, int)} and verify each of those results.
      @param dataIn            A Data object to be used as a source of data for conversion.
                               Used in a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType)</pre>.
      @param newSize           A non-negative size, or else the magic value <pre>-1</pre>.
                               Used in a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType)</pre>.
      @param newSPA            A valid SPA value, or else the magic value <pre>-1</pre>.
                               Used in a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType)</pre>.
      @param newMode           An atomic mode (<pre>(byte)'S'</pre> for scalar, <pre>(byte)'C'</pre> for complex, etc.), or else the magic value <pre>(byte)' '</pre>.
                               Used in a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType)</pre>.
      @param newType           An atomic type (<pre>(byte)'B'</pre> for byte, <pre>(byte)'S'</pre> for short, <pre>(byte)'D'</pre> for double, etc.), or else the magic value <pre>(byte)'_'</pre>.
                               Used in a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType)</pre>.
      @param expectedSize      Expected result of a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType).getSize()</pre>.
                               Ignored if <pre>expectedException</pre> is not <pre>null</pre>.
      @param expectedSPA       Expected result of a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType).getSPA()</pre>.
                               Ignored if <pre>expectedException</pre> is not <pre>null</pre>.
      @param expectedFormat    Expected result of a call to <pre>dataIn.convertTo(newSize, newSPA, newMode, newType).getFormat()</pre>.
                               Ignored if <pre>expectedException</pre> is not <pre>null</pre>.
      @param expectedException The exception which must be thrown during a call to <pre>dataIn(newSize, newSPA, newMode, newType)</pre>,
                               or else <pre>null</pre> if there must be no exception thrown.
   */
  private void _testOf_convertTo (Data dataIn,
                                 int newSize, int newSPA, char newMode, char newType,
                                 int expectedSize, int expectedSPA, String expectedFormat,
                                 Class<? extends Exception> expectedException
                                ) {
    String msg = "Data.convertTo("+newSize+","+newSPA+",'"+newMode+"','"+newType+"')";
    Data dataOut = null;
    try {
      dataOut = Data_convertTo(dataIn, newSize, newSPA, newMode, newType);
    } catch (Exception e) {
      if (expectedException==null) throw new RuntimeException(msg+" failed", e);
      if (expectedException.isInstance(e)) return;
      else fail(msg+" expected exception:<"+expectedException.getName()+"> but caught:<"+e+">");
    }
    if (expectedException!=null) fail(msg+" expected exception:<"+expectedException.getName()+"> but no exception was thrown");

    char originalType = (char)dataIn.getFormatType();
    int srcBufPopulatedLength = Data_numBytes(dataIn, dataIn.getSize());
    int dstBufWrittenLength = Data_numBytes(dataOut, dataOut.getSize());
    _testOfResultOf_convertTo(dataOut, msg, originalType, srcBufPopulatedLength, dstBufWrittenLength, expectedSize, expectedSPA, expectedFormat);

    /** Populate new Data objects using {@link Data#fillBufferFrom(Data, int)} and verify the results. */
    if (dataOut.getSize()>0) {
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize()-1, dataOut.getSize()-1, originalType);
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize()-1, dataOut.getSize(),   originalType); // will need larger buf
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize(),   dataOut.getSize()-1, originalType);
    }
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize(),   dataOut.getSize(),   originalType);
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize(),   dataOut.getSize()+1, originalType); // will need larger buf, so no need to zero-fill
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize()+1, dataOut.getSize(),   originalType);
    _testOf_fillBufferFrom(dataOut, 0, dataOut.getSize()+1, dataOut.getSize()+1, originalType); // will need to zero-fill
    if (dataOut.getSize()>0) {
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize()-1, dataOut.getSize()-1, originalType);
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize()-1, dataOut.getSize(),   originalType); // will need larger buf
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize(),   dataOut.getSize()-1, originalType);
    }
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize(),   dataOut.getSize(),   originalType);
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize(),   dataOut.getSize()+1, originalType); // will need larger buf, so no need to zero-fill
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize()+1, dataOut.getSize(),   originalType);
    _testOf_fillBufferFrom(dataOut, 3, dataOut.getSize()+1, dataOut.getSize()+1, originalType); // will need to zero-fill
  }

  /** Based on the format and content of a given Data object, construct a new Data object and populate
      it via a call to {@link Data#fillBufferFrom(Data, int)}, then verify the result.
      @param dataIn       A Data object to be used as a source of data.  The format of this object will
                          be used as the format of the new Data object.  Used in a call to
                          <pre>Data#fillBufferFrom(dataIn, copySize)</pre>.
      @param boff         Byte offset value for the buffer of the new Data object.
      @param initSize     Initial size (number of elements) for the buffer of the new Data object.
      @param copySize     Number of elements to write into the buffer of the new Data object.
                          Used in a call to <pre>Data#fillBufferFrom(dataIn, copySize)</pre>.
      @param originalType The format type of the Data object which was the source of the data in the
                          <pre>dataIn</pre> object.  This information is used to determine whether it is
                          practical to examine the contents of the buffer of the resulting Data object.
   */
  private void _testOf_fillBufferFrom (Data dataIn, int boff, int initSize, int copySize, char originalType) {
    int srcBufPopulatedLength = Data_numBytes(dataIn, Math.min(copySize, dataIn.getSize())); // these test cases are written to ensure this is true

    int expectedSize = Math.max(initSize, copySize);
    int expectedSPA = dataIn.getSPA();
    String expectedFormat = dataIn.getFormat();
    String msg = "new Data(\""+expectedFormat+"\", "+initSize+").fillBufferFrom(dataIn, "+copySize+")";

    Data dataOut = new Data(expectedFormat, initSize);
    if (boff>0) {
      dataOut.boff = boff;
      dataOut.setSize(initSize);
    }
    Arrays.fill(dataOut.getBuf(), (byte)'!'); // pre-fill with non-zero data so that correct zero-filling can be checked
    Data_fillBufferFrom(dataOut, dataIn, copySize);
    dataOut.size = expectedSize; // must maintain internal consistency
    int dstBufWrittenLength = Data_numBytes(dataIn, copySize);

    _testOfResultOf_convertTo(dataOut, msg, originalType, srcBufPopulatedLength, dstBufWrittenLength, expectedSize, expectedSPA, expectedFormat);
  }

  /** Verify that the attributes of a given Data object match expectations.  If practical, also verify
      that the contents of the buffer of the given Data object match expectations.
      @param dataOut               A Data object to be verified.
      @param msg                   Identifying message to be included with any AssertionError.
      @param originalType          The format type of the Data object which was the original source
                                   of the data in the <pre>dataOut</pre> object.  This information is
                                   used to determine whether it is practical to examine the contents
                                   of the buffer of the <pre>dataOut</pre> object.
      @param srcBufPopulatedLength The number of bytes of meaningful data which were available to be converted
                                   or copied during a preceding call to {@link Data#convertTo(int, int, char, char)}
                                   or {@link Data#fillBufferFrom(Data, int)}.  This information is only used if
                                   it is practical to examine the contents of the buffer of the <pre>dataOut</pre> object.
      @param dstBufWrittenLength   The number of bytes actually written during a preceding call to
                                   {@link Data#convertTo(int, int, char, char)} or {@link Data#fillBufferFrom(Data, int)}.
                                   This information is only used if it is practical to examine the contents
                                   of the buffer of the <pre>dataOut</pre> object.
      @param expectedSize          Expected result of a call to <pre>dataOut.getSize()</pre>.
      @param expectedSPA           Expected result of a call to <pre>dataOut.getSPA()</pre>.
      @param expectedFormat        Expected result of a call to <pre>dataOut.getFormat()</pre>.
   */
  private void _testOfResultOf_convertTo (Data dataOut, String msg, char originalType,
                                         int srcBufPopulatedLength, int dstBufWrittenLength,
                                         int expectedSize, int expectedSPA, String expectedFormat) {
    assertEquals(msg+".getSize()",   expectedSize,   dataOut.getSize());
    assertEquals(msg+".getSPA()",    expectedSPA,    dataOut.getSPA());
    assertEquals(msg+".getFormat()", expectedFormat, dataOut.getFormat());
    int expectedBPS = bpsFromType(expectedFormat.charAt(1)); // negative for nibbles and bits
    int expectedAPE = 1;
    int expectedBPE = expectedBPS * expectedSPA * expectedAPE;
    int expectedBufLength = dataOut.boff + ( expectedBPE>0 ? expectedBPE*expectedSize : (-expectedBPE*expectedSize+7)/8 );
    assertEquals(msg+".getBuf().length", expectedBufLength, dataOut.getBuf().length);

    if (originalType==dataOut.getFormatType()) {
      byte[] originalBuf =
        (originalType=='A') ? BB_STRING32 :
        (originalType=='B') ? BB_STRING32 :
        (originalType=='D') ? BB_DOUBLEARRAY32 : null;
      if (originalBuf==null) return;

      byte[] expectedBuf = Arrays.copyOfRange(Arrays.copyOfRange(originalBuf, 0, srcBufPopulatedLength), 0, dstBufWrittenLength);
      byte[] testableBuf = Arrays.copyOfRange(dataOut.getBuf(), dataOut.boff, dataOut.boff+dstBufWrittenLength);
      assertArrayEquals(msg+".getBuf()", expectedBuf, testableBuf);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The pack?()/unpack?() and get?()/set?()methods
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testPackB () {
    String scalar  = "SB";
    String complex = "CB";
    int    bps     = 1;
    byte[] values  = new byte[256];
    byte[] octets  = values;

    for (int i = 0; i < values.length; i++) {
      values[i] = (byte)(Byte.MIN_VALUE+i);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setB(i, values[i]);
      setComplex.setB(i, values[i]);
      packScalar.packB(j, values[i]);
      packComplex.packB(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setB("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setB("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packB("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packB("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getB("+i+") [scalar]",  values[i],   setScalar.getB(i));
      assertEquals(     "Data.getB("+i+") [complex]", values[i],  setComplex.getB(i));
      assertEquals(  "Data.unpackB("+i+") [scalar]",  values[i],  packScalar.unpackB(j));
      assertEquals(  "Data.unpackB("+i+") [complex]", values[i], packComplex.unpackB(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testPackI () {
    String  scalar  = "SI";
    String  complex = "CI";
    int     bps     = 2;
    short[] values  = new short[4096];
    byte[]  octets  = new byte[values.length*bps];

    for (int i=0,j=Short.MIN_VALUE; i < 1024; i++,j++) {
      values[i] = (short)j;
    }
    for (int i=1024,j=-1024; i < 3072; i++,j++) {
      values[i] = (short)j;
    }
    for (int i=0,j=Short.MAX_VALUE; i < 4096; i++,j--) {
      values[i] = (short)j;
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Convert.packI(octets, j, values[i]);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setI(i, values[i]);
      setComplex.setI(i, values[i]);
      packScalar.packI(j, values[i]);
      packComplex.packI(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setI("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setI("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packI("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packI("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getI("+i+") [scalar]",  values[i],   setScalar.getI(i));
      assertEquals(     "Data.getI("+i+") [complex]", values[i],  setComplex.getI(i));
      assertEquals(  "Data.unpackI("+i+") [scalar]",  values[i],  packScalar.unpackI(j));
      assertEquals(  "Data.unpackI("+i+") [complex]", values[i], packComplex.unpackI(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testPackL () {
    String  scalar  = "SL";
    String  complex = "CL";
    int     bps     = 4;
    int[]   values  = new int[4096];
    byte[]  octets  = new byte[values.length*bps];

    for (int i=0,j=Integer.MIN_VALUE; i < 1024; i++,j++) {
      values[i] = j;
    }
    for (int i=1024,j=-1024; i < 3072; i++,j++) {
      values[i] = j;
    }
    for (int i=0,j=Integer.MAX_VALUE; i < 4096; i++,j--) {
      values[i] = j;
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Convert.packL(octets, j, values[i]);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setL(i, values[i]);
      setComplex.setL(i, values[i]);
      packScalar.packL(j, values[i]);
      packComplex.packL(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setL("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setL("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packL("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packL("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getL("+i+") [scalar]",  values[i],   setScalar.getL(i));
      assertEquals(     "Data.getL("+i+") [complex]", values[i],  setComplex.getL(i));
      assertEquals(  "Data.unpackL("+i+") [scalar]",  values[i],  packScalar.unpackL(j));
      assertEquals(  "Data.unpackL("+i+") [complex]", values[i], packComplex.unpackL(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testPackX () {
    String  scalar  = "SX";
    String  complex = "CX";
    int     bps     = 8;
    long[]  values  = new long[4096];
    byte[]  octets  = new byte[values.length*bps];

    for (int i=0,j=0; i < 1024; i++,j++) {
      values[i] = Long.MIN_VALUE+j;
    }
    for (int i=1024,j=-1024; i < 3072; i++,j++) {
      values[i] = j;
    }
    for (int i=0,j=0; i < 4096; i++,j++) {
      values[i] = Integer.MAX_VALUE-j;
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Convert.packX(octets, j, values[i]);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setX(i, values[i]);
      setComplex.setX(i, values[i]);
      packScalar.packX(j, values[i]);
      packComplex.packX(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setX("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setX("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packX("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packX("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getX("+i+") [scalar]",  values[i],   setScalar.getX(i));
      assertEquals(     "Data.getX("+i+") [complex]", values[i],  setComplex.getX(i));
      assertEquals(  "Data.unpackX("+i+") [scalar]",  values[i],  packScalar.unpackX(j));
      assertEquals(  "Data.unpackX("+i+") [complex]", values[i], packComplex.unpackX(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testPackF () {
    String  scalar  = "SF";
    String  complex = "CF";
    int     bps     = 4;
    float[] values  = new float[4096];
    byte[]  octets  = new byte[values.length*bps];

    for (int i=0,j=0; i < 1024; i++,j++) {
      values[i] = -Float.MAX_VALUE+j;
    }
    for (int i=1024,j=-1024; i < 3072; i++,j++) {
      values[i] = j;
    }
    for (int i=0,j=0; i < 4096; i++,j++) {
      values[i] = Float.MAX_VALUE-j;
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Convert.packF(octets, j, values[i]);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setF(i, values[i]);
      setComplex.setF(i, values[i]);
      packScalar.packF(j, values[i]);
      packComplex.packF(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setF("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setF("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packF("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packF("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getF("+i+") [scalar]",  values[i],   setScalar.getF(i));
      assertEquals(     "Data.getF("+i+") [complex]", values[i],  setComplex.getF(i));
      assertEquals(  "Data.unpackF("+i+") [scalar]",  values[i],  packScalar.unpackF(j));
      assertEquals(  "Data.unpackF("+i+") [complex]", values[i], packComplex.unpackF(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testPackD () {
    String   scalar  = "SD";
    String   complex = "CD";
    int      bps     = 8;
    double[] values  = new double[4096];
    byte[]   octets  = new byte[values.length*bps];

    for (int i=0,j=0; i < 1024; i++,j++) {
      values[i] = -Double.MAX_VALUE+j;
    }
    for (int i=1024,j=-1024; i < 3072; i++,j++) {
      values[i] = j;
    }
    for (int i=0,j=0; i < 4096; i++,j++) {
      values[i] = Double.MAX_VALUE-j;
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Convert.packD(octets, j, values[i]);
    }

    Data setScalar   = new Data(scalar,  values.length);
    Data setComplex  = new Data(complex, values.length/2);
    Data packScalar  = new Data(scalar,  values.length);
    Data packComplex = new Data(complex, values.length/2);
    Data numScalar   = new Data(scalar,  values.length);
    Data numComplex  = new Data(complex, values.length/2);

    // Do all the set/pack calls first since we can then verify that there aren't
    // overlaps due to improper indexing.
    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      setScalar.setD(i, values[i]);
      setComplex.setD(i, values[i]);
      packScalar.packD(j, values[i]);
      packComplex.packD(j, values[i]);
      numScalar.setNumber(i, values[i]);
      numComplex.setNumber(i, values[i]);
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];

      for (int k = j; k < j+bps; k++) {
        assertEquals(     "Data.setD("+i+","+v+") @ "+k+" [scalar]",  octets[k],   setScalar.buf[  setScalar.boff+k]);
        assertEquals(     "Data.setD("+i+","+v+") @ "+k+" [complex]", octets[k],  setComplex.buf[ setComplex.boff+k]);
        assertEquals(    "Data.packD("+j+","+v+") @ "+k+" [scalar]",  octets[k],  packScalar.buf[ packScalar.boff+k]);
        assertEquals(    "Data.packD("+j+","+v+") @ "+k+" [complex]", octets[k], packComplex.buf[packComplex.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [scalar]",  octets[k],   numScalar.buf[  numScalar.boff+k]);
        assertEquals("Data.setNumber("+i+","+v+") @ "+k+" [complex]", octets[k],  numComplex.buf[ numComplex.boff+k]);
      }
    }

    for (int i=0,j=0; i < values.length; i++,j+=bps) {
      Number v = values[i];
      assertEquals(     "Data.getD("+i+") [scalar]",  values[i],   setScalar.getD(i));
      assertEquals(     "Data.getD("+i+") [complex]", values[i],  setComplex.getD(i));
      assertEquals(  "Data.unpackD("+i+") [scalar]",  values[i],  packScalar.unpackD(j));
      assertEquals(  "Data.unpackD("+i+") [complex]", values[i], packComplex.unpackD(j));
      assertEquals("Data.getNumber("+i+") [scalar]",  v,           numScalar.getNumber(i));
      assertEquals("Data.getNumber("+i+") [complex]", v,          numComplex.getNumber(i));
    }
  }

  public void testUnpackB () { } // tested with packB(..)
  public void testUnpackI () { } // tested with packI(..)
  public void testUnpackL () { } // tested with packL(..)
  public void testUnpackX () { } // tested with packX(..)
  public void testUnpackF () { } // tested with packX(..)
  public void testUnpackD () { } // tested with packX(..)

  public void testGetB () { } // tested with packB(..)
  public void testGetI () { } // tested with packI(..)
  public void testGetL () { } // tested with packL(..)
  public void testGetX () { } // tested with packX(..)
  public void testGetF () { } // tested with packX(..)
  public void testGetD () { } // tested with packX(..)

  public void testSetB () { } // tested with packB(..)
  public void testSetI () { } // tested with packI(..)
  public void testSetL () { } // tested with packL(..)
  public void testSetX () { } // tested with packX(..)
  public void testSetF () { } // tested with packX(..)
  public void testSetD () { } // tested with packX(..)

  public void testGetNumber () { } // tested with the various get?() methods
  public void testSetNumber () { } // tested with the various set?() methods

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The get?() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Miscellaneous methods
  //////////////////////////////////////////////////////////////////////////////////////////////////

  public void testPromoteType() {
                 // type1, type2, expected type
    char testData[] = {'D', 'D', 'D', // test promote to double (64-bit)
                       'X', 'D', 'D',
                       'D', 'L', 'D',
                       'I', 'D', 'D',
                       'D', 'B', 'D',
                       'P', 'D', 'D',
                       'X', 'F', 'D', // matches xm46x+ => D (to match max precision bits)
                       'F', 'L', 'F', // test promote to float (32-bit)
                       'I', 'F', 'F',
                       'F', 'B', 'F',
                       'P', 'F', 'F',
                       'X', 'L', 'X', // test promote to XLONG Integer (64-bit)
                       'I', 'X', 'X',
                       'X', 'B', 'X',
                       'L', 'I', 'L', // test promote to LONG Integer (32-bit)
                       'B', 'L', 'L',
                       'L', 'P', 'L',
                       'I', 'B', 'I', // test promote to Integer (short) (16-bit)
                       'P', 'I', 'I',
                       'P', 'B', 'B', // test promote to byte (8-bit)
                       'A', 'A', 'A',
                      };

    for (int i = 0; i < testData.length; i+=3) {
      assertEquals("promoteType("+testData[i]+","+testData[i+1]+")",
                   testData[i+2],
                   Data.promoteType(testData[i], testData[i+1]));
    }

    try {
      Data.promoteType('A', 'D');
      fail("expected promoteType('A', 'D') to throw exception as there is currently no "+
          "type promotion from non-numerical to ASCII midas data types");
    } catch (MidasException e) {
      // expected - test passed
    }
  }

  /** @since NeXtMidas 2.7.0 */
  public void testHashCode() {
    for (int ii = 0; ii < TESTDATA_EQUALS_HASHCODE.length; ii++) {
      Data[] darray = TESTDATA_EQUALS_HASHCODE[ii];
      Data data1 = darray[0];
      Data data2 = darray[1];
      Data data3 = darray[2];
      Data data4 = darray[3];
      assertEquals("hashCode is consistent "+ii, data1.hashCode(), data1.hashCode());
      assertEquals("hashCode is consistent "+ii, data4.hashCode(), data4.hashCode());
      assertTrue("hashCode test data is equal to each other", data1.equals(data2) && data2.equals(data3) && data1.equals(data3));
      assertEquals("hashCode is consistent with equals "+ii, data1.hashCode(), data2.hashCode());
      assertEquals("hashCode is consistent with equals "+ii, data2.hashCode(), data3.hashCode());
      assertEquals("hashCode is consistent with equals "+ii, data1.hashCode(), data3.hashCode());
    }
  }

  /** @since NeXtMidas 2.7.0 */
  public void testEquals() {
    for (int ii = 0; ii < TESTDATA_EQUALS_HASHCODE.length; ii++) {
      Data[] darray = TESTDATA_EQUALS_HASHCODE[ii];
      Data data1 = darray[0];
      Data data2 = darray[1];
      Data data3 = darray[2];
      Data data4 = darray[3];
      // test that it is reflexive: x.equals(x) must be true
      assertTrue("equals is reflexive "+ii, data1.equals(data1));
      assertTrue("equals same data "+ii, data1.equals(data2));
      // test that it is symmetric: x.equals(y) must be equal to y.equals(x)
      assertTrue("equals is symmetric "+ii, data2.equals(data1));
      // test that it is transitive: if x.equals(y), and y.equals(z), then x.equals(z)
      assertTrue("equals is transitive "+ii, data2.equals(data3) && data1.equals(data3)); // FYI: previous test asserts data1.equals(data2)
      // test that it is consistent: multiple invocations of x.equals(y) consistently returns same value
      assertTrue("equals is consistent "+ii, data1.equals(data2) && data1.equals(data2));
      // for any non-null reference x, x.equals(null) must return false
      assertFalse("equals is false for null "+ii, data1.equals(null) && data2.equals(null) && data3.equals(null) && data4.equals(null));

      // negative test cases (test that things are NOT equal to each other)
      assertFalse("equals is false for different Data "+ii, data1.equals(data4));
      // test that it is symmetric for negative case
      assertFalse("equals is false (symmetric) for different Data "+ii, data4.equals(data1));
      assertFalse("equals is false (transitive) for different Data "+ii, data4.equals(data2) || data3.equals(data4));
      assertFalse("equals is false (consistent) for different Data "+ii, data1.equals(data4) || data4.equals(data3));
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The copy(), cast?() and uncast() methods
  //////////////////////////////////////////////////////////////////////////////////////////////////

  public void testCopy () {
    Data[] dataBuffers = { new Data(INITIAL_B), new Data(INITIAL_I),
                           new Data(INITIAL_L), new Data(INITIAL_X),
                           new Data(INITIAL_F), new Data(INITIAL_D) };

    for (Data data : dataBuffers) {
      char type  = (char)data.getFormatType();
      Data copy = data.copy();

      assertEquals( "copy() for type "+type+" (equal)",             data,     copy);
      assertNotSame("copy() for type "+type+" (different buffers)", data.buf, copy.buf);
    }
  }

  public void testCastD () {
    Data[] dataBuffers = { new Data(INITIAL_B), new Data(INITIAL_I),
                           new Data(INITIAL_L), new Data(INITIAL_X),
                           new Data(INITIAL_F), new Data(INITIAL_D) };

    for (Data data : dataBuffers) {
      char type  = (char)data.getFormatType();
      Data dataD = data.copy();      double[] arrayD = dataD.castD(true);
      Data dataF = data.copy();      float[]  arrayF = dataF.castF(true);
      Data dataX = data.copy();      long[]   arrayX = dataX.castX(true);
      Data dataL = data.copy();      int[]    arrayL = dataL.castL(true);
      Data dataI = data.copy();      short[]  arrayI = dataI.castI(true);
      Data dataB = data.copy();      byte[]   arrayB = dataB.castB(true);

      assertArrayEquals("castD(true) for type "+type, INITIAL_D, arrayD);
      assertArrayEquals("castF(true) for type "+type, INITIAL_F, arrayF);
      assertArrayEquals("castX(true) for type "+type, INITIAL_X, arrayX);
      assertArrayEquals("castL(true) for type "+type, INITIAL_L, arrayL);
      assertArrayEquals("castI(true) for type "+type, INITIAL_I, arrayI);
      assertArrayEquals("castB(true) for type "+type, INITIAL_B, arrayB);

      for (int i = 0; i < UPDATE_B.length; i++) {
        arrayD[i] = UPDATE_B[i];
        arrayF[i] = UPDATE_B[i];
        arrayX[i] = UPDATE_B[i];
        arrayL[i] = UPDATE_B[i];
        arrayI[i] = UPDATE_B[i];
        arrayB[i] = UPDATE_B[i];
      }
      dataD.uncast(arrayD, true);
      dataF.uncast(arrayF, true);
      dataX.uncast(arrayX, true);
      dataL.uncast(arrayL, true);
      dataI.uncast(arrayI, true);
      dataB.uncast(arrayB, true);

      for (int i = 0; i < FINAL_B.length; i++) {
        assertEquals("uncast(true) following castD(..) for type "+type+" value at "+i, FINAL_B[i], dataD.getAtomAt(i).toB());
        assertEquals("uncast(true) following castF(..) for type "+type+" value at "+i, FINAL_B[i], dataF.getAtomAt(i).toB());
        assertEquals("uncast(true) following castX(..) for type "+type+" value at "+i, FINAL_B[i], dataX.getAtomAt(i).toB());
        assertEquals("uncast(true) following castL(..) for type "+type+" value at "+i, FINAL_B[i], dataL.getAtomAt(i).toB());
        assertEquals("uncast(true) following castI(..) for type "+type+" value at "+i, FINAL_B[i], dataI.getAtomAt(i).toB());
        assertEquals("uncast(true) following castB(..) for type "+type+" value at "+i, FINAL_B[i], dataB.getAtomAt(i).toB());
      }
    }

    // test case for BUG 2374 COMPARE: Fails when comparing type 3000 files
    Data nhdata = new Data("NH", 1);
    boolean gotCastException = false, gotUncastException = false;
    float[] farr = null;
    try { nhdata.castF(false);        } catch (MidasException ex) { gotCastException   = true; }
    try { nhdata.uncast(farr, false); } catch (MidasException ex) { gotUncastException = true; }
    assertTrue("castF(false) for NH format (should throw exception)", gotCastException);
    assertTrue("uncast(float[], false) for NH format (should throw exception)", gotUncastException);
    // end test case for BUG 2374 COMPARE: Fails when comparing type 3000 files
  }

  public void testCastF () { // mostly tested in testCastD
    //start test case for BUG 2433
    /*
    Data dataF = new Data(INITIAL_F);
    int expected_size = INITIAL_F.length - 1;
    dataF.setSize(expected_size);
    float[]  arrayF = dataF.castF(true);
    assertEquals("castF BUG 2433: ", expected_size, arrayF.length);
    //*/
    //end test case for BUG 2433
  }
  public void testCastX () { }  // tested in testCastD
  public void testCastL () { }  // tested in testCastD
  public void testCastI () { }  // tested in testCastD
  public void testCastB () { }  // tested in testCastD
  public void testUncast () { } // tested in testCastD

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // The ?Size() methods (getSize(), setSize())
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testSetSize () {
    Data[] dataBuffers = { new Data(INITIAL_B), new Data(INITIAL_I),
                           new Data(INITIAL_L), new Data(INITIAL_X),
                           new Data(INITIAL_F), new Data(INITIAL_D) };

    for (Data data : dataBuffers) {
      _testSetSize(data.copy(), INITIAL_B.length-5);
      _testSetSize(data.copy(), INITIAL_B.length  );
      _testSetSize(data.copy(), INITIAL_B.length+5);
    }
  }

  private void _testSetSize (Data data, int size) {
    char type = (char)data.getFormatType();

    assertEquals("Before setSize("+size+") for type "+type+" (size)", INITIAL_B.length, data.getSize());

    for (int i = 0; i < INITIAL_B.length; i++) {
      assertEquals("Before setSize("+size+") for type "+type+" (value at "+i+")", INITIAL_B[i], data.getAtomAt(i).toB());
    }

    data.setSize(size);
    assertEquals("After setSize("+size+") for type "+type+" (size)", size, data.getSize());

    for (int i = 0; (i < INITIAL_B.length) && (i < size); i++) {
      assertEquals("After setSize("+size+") for type "+type+" (value at "+i+")", INITIAL_B[i], data.getAtomAt(i).toB());
    }
    for (int i = INITIAL_B.length; i < size; i++) {
      assertEquals("After setSize("+size+") for type "+type+" (value at "+i+")", 0, data.getAtomAt(i).toB());
    }
  }

  /** Tests that serialization and deserialization works as expected */
  public void testSerializable () {
    Data simple = new Data("SF", 4, 12.34);
    testSerializeDeserialize(simple,"Data with header set to null");
    simple.setHeader("simple");
    testSerializeDeserialize(simple,"Data with header set to non-Time value");
    simple.setHeader(Time.currentTime());
    testSerializeDeserialize(simple,"Data without header set to Time as expected");
  }

  private void testSerializeDeserialize (Data data, String message) {
    File ramFile = null;
    try {
      ramFile = File.createTempFile("dataserializationtest", "temp");
      FileOutputStream fos = new FileOutputStream(ramFile);
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(data);
      oos.flush();
      fos.close();
      FileInputStream fis = new FileInputStream(ramFile);
      ObjectInputStream ois = new ObjectInputStream(fis);
      Data  sfData    = (Data)ois.readObject();
      fis.close();
      assertEquals(message,data.toString(), sfData.toString());
      Object expectedHeader = ((data.getHeader() != null) && (data.getHeader() instanceof Time)) ?
          data.getHeader() : null;
      assertEquals(message,expectedHeader, sfData.getHeader());
      assertEquals(message,data.getHeaderTime(), sfData.getHeaderTime());
    }
    catch (Exception e) {
      fail("Unexpected Exception during serialization:"+e);
    }
    finally {
      ramFile.delete();
    }
  }

  private void arrayListConstructor () {
    assertEquals(getByteVals(), new Data(getByteVals()).toList(Byte.class));
    assertEquals(getShortVals(), new Data(getShortVals()).toList(Short.class));
    assertEquals(getFloatVals(), new Data(getFloatVals()).toList(Float.class));
    assertEquals(getIntVals(), new Data(getIntVals()).toList(Integer.class));
    assertEquals(getLongVals(), new Data(getLongVals()).toList(Long.class));
    assertEquals(getDoubleVals(), new Data(getDoubleVals()).toList(Double.class));
  }

  /**
   * This will test that each list can have a list of the other type created.
   */
  public void testToList () {
    compareLists(new Data(getShortVals()));
    compareLists(new Data(getFloatVals()));
    compareLists(new Data(getIntVals()));
    compareLists(new Data(getLongVals()));
    compareLists(new Data(getDoubleVals()));
    compareLists(new Data(getByteVals()));
  }

  private void compareLists(Data data) {
    assertEquals(data.toList(Short.class), getShortVals());
    assertEquals(data.toList(Double.class), getDoubleVals());
    assertEquals(data.toList(Integer.class), getIntVals());
    assertEquals(data.toList(Float.class), getFloatVals());
    assertEquals(data.toList(Long.class), getLongVals());
    assertEquals(data.toList(Byte.class), getByteVals());

  }

  private List<Long> getLongVals () {
    if(longVals == null) {
      longVals = new ArrayList<>();
      longVals.add(1l);
      longVals.add(2l);
      longVals.add(3l);
    }
    return longVals;
  }

  private List<Integer> getIntVals () {
    if (intVals == null) {
      intVals = new ArrayList<>();
      intVals.add(1);
      intVals.add(2);
      intVals.add(3);
    }
    return intVals;
  }

  private List<Byte> getByteVals () {
    if (byteVals==null) {
      byteVals = new ArrayList<>();
      byteVals.add((byte) 1);
      byteVals.add((byte) 2);
      byteVals.add((byte) 3);
    }
    return byteVals;
  }

  private List<Short> getShortVals () {
    if (shortVals==null) {
      shortVals = new ArrayList<>();
      shortVals.add((short) 1);
      shortVals.add((short) 2);
      shortVals.add((short) 3);
    }
    return shortVals;
  }

  private List<Float> getFloatVals () {
    if (floatVals==null) {
      floatVals = new ArrayList<>();
      floatVals.add(1f);
      floatVals.add(2f);
      floatVals.add(3f);
    }
    return floatVals;
  }

  private List<Double> getDoubleVals () {
    if (doubleVals == null) {
      doubleVals = new ArrayList<>();
      doubleVals.add(1d);
      doubleVals.add(2d);
      doubleVals.add(3d);
    }
    return doubleVals;
  }

  public void testFromList () { } // tested in testConstructor
  public void testGetSize () { } // tested in testSetSize
}
