Javolution ByteBuffer question

2.9k Views Asked by At

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?

3

There are 3 best solutions below

1
On

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);
     }
}
2
On

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.

2
On

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.