1

I have the following implementation with Javolution:

public class RunScan extends Struct
{
    public final Signed32 numOfClusters = new Signed32();
    public final ClusterData[] clusters;
    public final Signed32 numOfRecons = new Signed32();
    public final ReconData[] recons ;

    public RunScan (int numOfClusters, int numOfRecons)
    {
        this.numOfClusters.set(numOfClusters);
        this.numOfRecons.set(numOfRecons);
        clusters = array(new ClusterData[numOfClusters]);
        recons = array(new ReconData[numOfRecons]);      
    }
}

public class ClusterData extends Struct
{
    public final UTF8String scanType = new UTF8String(CommInterfaceFieldConstants.SCAN_TYPE_SIZE);
    public final UTF8String patientId = new UTF8String(CommInterfaceFieldConstants.PATIENT_ID_SIZE);
.
.
.
}

public class ReconData extends Struct
{
    public final UTF8String patientId = new UTF8String(CommInterfaceFieldConstants.PATIENT_ID_SIZE);
    public final UTF8String scanSeriesId = new UTF8String(CommInterfaceFieldConstants.SCAN_SERIES_ID_SIZE);
.
.
.
}

In our communication class, before we put data onto socket, we need to get the bytes[] of the RunScan object but we get BufferUnderflowException in the line with "//<<<<<<<":

private byte[] getCmdBytes(Struct scCmd)
    {
        ByteBuffer cmdBuffer = scCmd.getByteBuffer();
        int cmdSize = scCmd.size();

        byte[] cmdBytes = new byte[cmdSize];
        if (cmdBuffer.hasArray()) 
        {
            int offset = cmdBuffer.arrayOffset() + scCmd.getByteBufferPosition();
            System.arraycopy(cmdBuffer.array(), offset, cmdBytes, 0, cmdSize);            
        } 
        else 
        {
            String msg = "\n\ncmdBufferRemaining=" + cmdBuffer.remaining() + ", cmdBytesSize=" + cmdBytes.length + "\n\n";
            System.out.println(msg);
            cmdBuffer.position(scCmd.getByteBufferPosition());
            cmdBuffer.get(cmdBytes); //<<<<<<<<<< underFlowException         
        }

        return cmdBytes;
    }

This method works in other cases. The exception happens because this line,

ByteBuffer cmdBuffer = scCmd.getByteBuffer();

only returns a 8 bytes (from the remaining() method) ByteBuffer of the RunScan object which are those two Signed32 fields, I think. But this line,

int cmdSize = scCmd.size();

returns a right length of the RunScan object which includes the size of those two arrays.

If I create those two array at the time I declare them (not "new" them in the constructor) with hard coded length, it works fine without any exception.

Anybody can help me figure out what's wrong with our implementation?

Jonas
  • 121,568
  • 97
  • 310
  • 388
5YrsLaterDBA
  • 33,370
  • 43
  • 136
  • 210

3 Answers3

2

I ran into a similar situation with my code. Generally, with the current Struct object, you cannot have a variable length array defined in the same struct as the member that contains the number of elements in the array.

Try something like this:

public class RunScanHeader extends Struct
{
    public final Signed32 numOfClusters = new Signed32();
    public final Signed32 numOfRecons = new Signed32();
}

public class RunScanBody extends Struct
{
    public final ClusterData[] clusters;
    public final ReconData[] recons ;

    public RunScan (int numOfClusters, int numOfRecons)
    {
        clusters = array(new ClusterData[numOfClusters]);
        recons = array(new ReconData[numOfRecons]);      
    }
}

You'll then need a two phase approach to read and write, first read/write the header data, then read/write the body data.

Sorry I don't have more details at this time, if you can't solve this, let me know and I'll dig back through my code.

Gordon
  • 1,210
  • 5
  • 16
  • 23
  • Thank you for your help. I will test it will our server side is ready. – 5YrsLaterDBA Aug 04 '11 at 20:32
  • As a general, non-specific library topic, consider ensuring the implementation is client/server platform-agnostic when dealing with raw byte[] type data. If the bytes represent numbers for example, endianess has to be considered. In general, I up-voted this answer because it spoke to the (more performant/consistent) method of sending first a header indicating the length of what is coming, followed by the (variable) data package. This allows the server to read a fixed header, then dynamically allocate for content and read completely/efficiently. – Darrell Teague Jul 31 '14 at 14:33
1

The initialization order is important has it defines the position of each field. Either your initialization is done when the field is declared (most common case). Or if you do it in the constructor you have to remember that the constructor is called after the member initialization. Here is an example with initialization done in the constructor:

 public class RunScan extends Struct {
     public final Signed32 numOfClusters;
     public final ClusterData[] clusters;
     public final Signed32 numOfRecons;
     public final ReconData[] recons ;

     public RunScan (int numOfClusters, int numOfRecons) {
        // Initialization done in the constructor for all members 
        // Order is important, it should match the declarative order to ensure proper positioning.
        this.numOfClusters = new Signed32();  
        this.clusters = array(new ClusterData[numOfClusters]);
        this.numOfRecons = new Signed32();
        this.recons = array(new ReconData[numOfRecons]);

        // Only after all the members have been initialized the set method can be used.
        this.numOfClusters.set(numOfClusters);
        this.numOfRecons.set(numOfRecons);
     }
}
Dautelle
  • 11
  • 1
  • thank you for help. With your suggestion, the UnderflowException was avoided but I still couldn't get the right value from the ClusterData array (the numOfCluster value is good.) when I convert the ByteBuffer into byte[] in order to put onto the Socket. I have tried with Jimn235's suggestion but still the same. So I have to write my own method to manually create a ByteBuffer and copy all the contents of the RunScan into that ByteBuffer and then convert that ByteBuffer into byte[]. – 5YrsLaterDBA Aug 15 '11 at 14:26
1

get() will move the position of the ByteBuffer.

scCmd.getByteBuffer().slice().get(dest) might solve your issue with moving the position and unintended side effects.

scCmd.getByteBuffer().duplicate().get(dest) might also solve your issue if slice() produces the wrong picture of the origin buffer.

Additionally, it appears as though scCmd.getByteBuffer() creates a redundant reference and you are calling the source and child reference in the same method.

If scCmd.getByteBuffer() is already passing you a slice(), your redundant access to these methods is certainly going to do something other than what you planned.

dandan78
  • 13,328
  • 13
  • 64
  • 78
jimn235
  • 11
  • 2
  • Hi Jimn235, thank you for your suggestion. Please refer to my comments on Dautelle's reply. I couldn't see a different result by using your suggestions. – 5YrsLaterDBA Aug 15 '11 at 15:31
  • im interested in your copy code, if only to see if it can made be simpler than your description. without the whole codebase it is hard to know what is behind the facade you have around buffers. – jimn235 Aug 15 '11 at 16:42