0
\$\begingroup\$

I have a class in which I am passing certain parameters through the constructor and then using those parameters to make one final byte array with a proper format (header + data) and it works fine:

public final class Frame {
  private final byte addressedCenter;
  private final byte version;
  private final Map<byte[], byte[]> keyDataHolder;
  private final long location;
  private final long locationFrom;
  private final long locationOrigin;
  private final byte partition;
  private final byte copy;

  public Frame(byte addressedCenter, byte version,
      Map<byte[], byte[]> keyDataHolder, long location, long locationFrom,
      long locationOrigin, byte partition, byte copy) {
    this.addressedCenter = addressedCenter;
    this.version = version;
    this.keyDataHolder = keyDataHolder;
    this.location = location;
    this.locationFrom = locationFrom;
    this.locationOrigin = locationOrigin;
    this.partition = partition;
    this.copy = copy;
  }

  public byte[] serialize() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(getBufferUsed(keyDataHolder)).order(ByteOrder.BIG_ENDIAN);

    // header layout
    byteBuffer.put(addressedCenter).put(version).putInt(keyDataHolder.size())
        .putInt(getBufferUsed(keyDataHolder)).putLong(location).putLong(locationFrom)
        .putLong(locationOrigin).put(partition).put(copy);

    // now the data layout
    for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
      byte keyType = 0;
      byte[] key = entry.getKey();
      byte[] value = entry.getValue();
      byte keyLength = (byte) key.length;
      short valueLength = (short) value.length;

      ByteBuffer dataBuffer = ByteBuffer.wrap(value);
      long timestamp = valueLength > 10 ? dataBuffer.getLong(2) : System.currentTimeMillis();

      byteBuffer.put(keyType).put(keyLength).put(key).putLong(timestamp).putShort(valueLength)
          .put(value);
    }

    return byteBuffer.array();
  }

  // this method is returning only `locationFrom`
  public static long getLocationFrom(final byte[] byteArray) {
    ByteBuffer bb = ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN);
    bb.rewind();

    byte addressedCenter = bb.get();
    byte version = bb.get();
    int numOfRecords = bb.getInt();
    int bufferUsed = bb.getInt();
    long location = bb.getLong();
    long locationFrom = bb.getLong();
    long locationOrigin = bb.getLong();
    byte partition = bb.get();
    byte copy = bb.get();

    byte keyType = bb.get();
    byte keyLength = bb.get();
    byte[] extractKeyValue = new byte[keyLength];
    bb.get(extractKeyValue);
    String key = new String(extractKeyValue, StandardCharsets.UTF_8);
    long timestamp = bb.getLong();
    short dataSize = bb.getShort();

    if (dataSize >= 16) {
      location = bb.getLong();
      locationFrom = bb.getLong();
    } else {
        // log that we have different size and we cannot extract
        // location and locationFrom it.
    }
    return locationFrom;
  } 

  // this method is returning only `location`
  public static long getLocation(final byte[] byteArray) {
    ByteBuffer bb = ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN);
    bb.rewind();

    byte addressedCenter = bb.get();
    byte version = bb.get();
    int numOfRecords = bb.getInt();
    int bufferUsed = bb.getInt();
    long location = bb.getLong();
    long locationFrom = bb.getLong();
    long locationOrigin = bb.getLong();
    byte partition = bb.get();
    byte copy = bb.get();

    byte keyType = bb.get();
    byte keyLength = bb.get();
    byte[] extractKeyValue = new byte[keyLength];
    bb.get(extractKeyValue);
    String key = new String(extractKeyValue, StandardCharsets.UTF_8);
    long timestamp = bb.getLong();
    short dataSize = bb.getShort();

    if (dataSize >= 16) {
      location = bb.getLong();
      locationFrom = bb.getLong();
    } else {
        // log that we have different size and we cannot extract
        // location and locationFrom it.
    }
    return location;
  }       

  // 36 + dataSize + 1 + 1 + keyLength + 8 + 2;
  private int getBufferUsed(final Map<byte[], byte[]> keyDataHolder) {
    int size = 36;
    for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
      size += 1 + 1 + 8 + 2;
      size += entry.getKey().length;
      size += entry.getValue().length;
    }
    return size;
  }  
}

Question

Now once I call serialize() method of above class, it will give me actual packed byteArray:

Frame frame = new Frame(......);
byte[] packedByteArray = frame.serialize();
// send packedByteArray to some other system
.......

byte[] byteArray = getFromOtherSystem();
long locationFrom = Frame.getLocationFrom(byteArray);
long location = Frame.getLocation(byteArray);

Now once I have this packedByteArray, I send it to some other system and then that system will return me back another byteArray which is packed in the same format as shown above in the serialize method. Now I need to use this byteArray and extract all the individual fields from it so I wrote the getLocationFrom method that takes the byteArray as the parameter and it will return me locationFrom field value only for now. When the other system sends me back the byteArray, its data size should be around 16 only and actual data will have two long values in it and data will always be in the same format when it comes from other system.

Right now my getLocationFrom method just returns locationFrom field only but if I want to get location or locationOrigin or version or other fields then should I add respective static methods in my DataFrame class? If I do that, then I will be repeating all the deserialization logic in all the static methods for each fields.

How should I design this Frame class so that it can take all the parameters to make one packedByteArray (through the constructor I am doing right now) and also it should be able to take byteArray parameter as well and give me all other fields by deserializing this? Just like I am doing for locationFrom field.

I am mainly trying to see how I can design my class so that if I asks for individual fields value given a byteArray, it should be able to give me those values back. Also, it should be able to serialize everything into one byteArray given all the fields.

\$\endgroup\$
1
\$\begingroup\$

Instead of shuffling the byte array around all the time, I'd rather create an additional Frame constructor, which operates on the byte array, performs the deserialization and sets all internal variables to the values from that byte array.

Then, you'd just parse the answer into an object and use the object for all later references:

byte[] byteArray = getFromOtherSystem();
Frame answerFrame = new Frame(byteArray);  // or a static factory method, or whatever
long locationFrom = answerFrame.locationFrom;
long location = answerFrame.location;

Trying to stick that constructor together after the comment:

public Frame(byte[] byteArray) {
    ByteBuffer bb = ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN);
    bb.rewind();

    this.addressedCenter = bb.get();
    this.version = bb.get();
    this.numOfRecords = bb.getInt();
    this.bufferUsed = bb.getInt();
    this.location = bb.getLong();
    this.locationFrom = bb.getLong();
    this.locationOrigin = bb.getLong();
    this.partition = bb.get();
    this.copy = bb.get();

    this.keyDataHolder = new HashMap<>();
    // Add code here to deserialize the map contents and add them to the map
    // this goes too much into detail for me ;-)
}

As to your compilation error: when you are working with final fields, each class constructor must ensure, that all fields are initialized. This initialization of all fields is the main point of the answer: deserialize the complete buffer once and then take all further interaction with the answer frame back to object level.

\$\endgroup\$
  • \$\begingroup\$ If I create another Frame constructor that takes byte array parameter, then it asks me to initialize other final fields as well in the same constructor? Or I am missing something? Can you provide an example of this? Because I tried this and it gave me compilation error asking me to initialize all other fields which are final in the same class. \$\endgroup\$ – david Jan 27 '17 at 5:24
  • \$\begingroup\$ See the edit for an answer. \$\endgroup\$ – mtj Jan 27 '17 at 6:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.