com.nordea.oss.copybook.serializers.PackedFirstLevelMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of copybook4java Show documentation
Show all versions of copybook4java Show documentation
CopyBook serializer and deserializer for Java where CopyBook lines are used to annotate a normal Java class
The newest version!
/*
* Copyright (c) 2015. Troels Liebe Bentsen
* Copyright (c) 2016. Nordea Bank AB
* Licensed under the MIT license (LICENSE.txt)
*/
package com.nordea.oss.copybook.serializers;
import com.nordea.oss.ByteUtils;
import com.nordea.oss.copybook.exceptions.CopyBookException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class PackedFirstLevelMapper extends CopyBookMapperBase {
private int maxBitmapSize;
private byte separatorByte;
private int bitmapBlockSize;
private int packingItemsCount;
public void initialize(CopyBookSerializerConfig config) {
super.initialize(config);
this.bitmapBlockSize = 8;//config.getBitmapBlockSize(); //TODO: Implement
this.packingItemsCount = countPackingItems(config.getFields());
int maxBitmapBlocks = packingItemsCount / (bitmapBlockSize * 8 - 1) + 1;
this.maxBitmapSize = bitmapBlockSize * maxBitmapBlocks;
this.separatorByte = '\u000b';//config.getSeparatorByte(); //TODO: Implement
}
private int countPackingItems(List fields) {
int count = 0;
for(CopyBookField field : fields) {
if(field.isArray()) {
count += field.getMaxOccurs();
} else {
count++;
}
}
return count;
}
@Override
public byte[] serialize(T obj) {
PackedBuffer buffer = new PackedBuffer(this.maxRecordSize + packingItemsCount, this.maxBitmapSize, this.bitmapBlockSize, this.separatorByte);
writeFields(buffer, this.fields, obj, true);
return buffer.array();
}
private void writeFields(PackedBuffer buffer, List fields, Object rootObj, boolean rootLast) {
for(CopyBookField field : fields) {
boolean last = rootLast && field.isLast();
if(field.isArray()) {
Object array = field.getObject(rootObj);
if(field.hasSubCopyBookFields()) {
// Complex array types fx. Request[]
if(field.getLevel() == 0) {
for(int j = 0; j < field.getMinOccurs(); j++) {
Object item = array != null ? field.getObject(rootObj, array, j) : null;
if(item != null) {
writeFields(buffer, field.getSubCopyBookFields(), item, true);
} else {
buffer.put(null, true); // Increment bitmapIndex
}
}
} else {
for(int j = 0; j < field.getMinOccurs(); j++) {
Object item = array != null ? field.getObject(rootObj, array, j) : null;
writeFields(buffer, field.getSubCopyBookFields(), item, last && field.getMinOccurs() - 1 == j);
}
}
} else {
// Simple array types, fx. int[]
for(int j = 0; j < field.getMinOccurs(); j++) {
writeField(buffer, field, field.getObject(rootObj, array, j), last && field.getMinOccurs() - 1 == j);
}
}
} else if(field.hasSubCopyBookFields()) {
// Complex type fx, Request
Object item = field.getObject(rootObj);
if(field.getLevel() == 0) {
if(item != null) {
writeFields(buffer, field.getSubCopyBookFields(), item, true);
} else {
buffer.put(null, true); // write separator
}
} else {
writeFields(buffer, fields, item, last);
}
} else {
// Simple type fx. int, String or types we support with TypeConverters
writeField(buffer, field, field.getObject(rootObj), last);
}
}
}
private void writeField(PackedBuffer buffer, CopyBookField field, Object valueObj, boolean last) {
if(field.getLevel() == 0) {
buffer.put(field.getBytes(null, valueObj, false), true); // No padding with separator
} else if(last) {
byte[] valueBytes = field.getBytes(null, valueObj, false); // Don't pad if it's the last field
if(valueBytes == null) {
valueBytes = field.getBytes(null, valueObj, true); // Except if it's null
}
buffer.put(valueBytes, true); // With separator
} else {
buffer.put(field.getBytes(null, valueObj, true), false); // With padding without separator
}
}
@Override
public T deserialize(byte[] bytes, Class type) {
try {
T obj = type.newInstance();
PackedBuffer buffer = new PackedBuffer(bytes, this.maxBitmapSize, this.bitmapBlockSize, this.separatorByte);
readFields(this.fields, buffer, obj, true);
return obj;
} catch (IllegalAccessException | InstantiationException e) {
throw new CopyBookException("Failed to create new object", e);
}
}
private void readFields(List fields, PackedBuffer buffer, Object obj, boolean rootLast) {
for(CopyBookField field : fields) {
boolean last = rootLast && field.isLast();
if(debug) { System.out.println("read " + field.getFieldName()); }
if(field.isArray()) {
int arraySize = field.getLevel() == 0 ? buffer.getArraySize(field.getMinOccurs()) : field.getMinOccurs();
Object array = field.createArrayObject(obj, arraySize);
if(field.hasSubCopyBookFields()) {
// Complex array types fx. Request[]
if (field.getLevel() == 0) {
for(int j = 0; j < arraySize; j++) {
readFields(field.getSubCopyBookFields(), buffer, field.createObject(array, j), true);
}
// Move bitmap index to the end of the array
buffer.incBitmapIndex(field.getMinOccurs() - arraySize);
} else {
for(int j = 0; j < arraySize; j++) {
readFields(field.getSubCopyBookFields(), buffer, field.createObject(array, j), last && field.getMinOccurs() - 1 == j);
}
}
} else {
// Simple array types, fx. int[]
if(field.getLevel() == 0) {
for(int i = 0; i < arraySize; i++) {
field.setBytes(array, i, buffer.get(field.getSize(), true), false);
}
// Move bitmap index to the end of the array
buffer.incBitmapIndex(field.getMinOccurs() - arraySize);
} else {
for(int i = 0; i < arraySize; i++) {
boolean isLast = last && field.getMinOccurs() - 1 == i;
field.setBytes(array, i, buffer.get(field.getSize(), isLast), !isLast);
}
}
}
} else {
if(field.hasSubCopyBookFields()) {
// Complex type fx, Request
readFields(field.getSubCopyBookFields(), buffer, field.createObject(obj), field.getLevel() == 0 || last);
} else {
// Simple type fx. int, String or types we support with TypeConverters
if(field.getLevel() == 0 || last) {
field.setBytes(obj, buffer.get(field.getSize(), true), false);
} else {
field.setBytes(obj, buffer.get(field.getSize(), false), true);
}
}
}
}
}
private class PackedBuffer {
private ByteBuffer buffer;
private byte[] bitmapBytes;
private int bitmapIndex = 0;
private int maxUsedBit = 0;
private int bitmapBlockSize;
private byte separatorByte;
private int bitmapSize;
private int bitmapMaxSize;
public PackedBuffer(int size, int bitmapMaxSize, int bitmapBlockSize, byte separatorByte) {
this.buffer = ByteBuffer.wrap(new byte[size]);
this.bitmapBytes = new byte[bitmapMaxSize];
this.bitmapBlockSize = bitmapBlockSize;
this.separatorByte = separatorByte;
}
public PackedBuffer(byte[] bytes, int bitmapMaxSize, int bitmapBlockSize, byte separatorByte) {
this.buffer = ByteBuffer.wrap(bytes);
this.bitmapBlockSize = bitmapBlockSize;
this.separatorByte = separatorByte;
this.bitmapMaxSize = bitmapMaxSize;
this.bitmapSize = getBitMapSize(bytes);
this.bitmapBytes = new byte[this.bitmapSize];
this.buffer.get(this.bitmapBytes);
}
public int getArraySize(int maxOccurs) {
int size = 0;
for (int i = 0; i < maxOccurs; i++) {
if (getBitInBitmap(this.bitmapBytes, this.bitmapIndex + i, this.bitmapSize)) {
size = i + 1;
if(debug) { System.out.print("+"); }
} else {
if(debug) { System.out.print("-"); }
}
}
if(debug) { System.out.println(size); }
/* TODO: Calculate the number of set bits with bit masking and Long.numberOfTrailingZeros as this should be quicker
String befor = debugBitmap(this.bitmapBytes, 0, 8);
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.put(this.bitmapBytes);
buffer.flip();//need flip
long stuff = buffer.getLong();
long mask = (Long.MAX_VALUE >> bitmapIndex - 1) & (Long.MAX_VALUE << (bitmapSize * 8 - bitmapIndex - maxOccurs));
long num2 = stuff & mask;
long size2 = num2 > 0 ? 8 * 8 - Long.numberOfTrailingZeros(num2) - bitmapIndex : 0;
long num3 = (stuff >> (8*8 - (bitmapIndex + maxOccurs))) & (Long.MAX_VALUE >> (8*8 - (bitmapIndex + maxOccurs)));
long size3 = maxOccurs - Long.numberOfTrailingZeros(num3);
ByteBuffer buffer2 = ByteBuffer.allocate(Long.BYTES);
buffer2.putLong(num3);
byte[] testbytes = buffer2.array();
String after = debugBitmap(testbytes, 0, 8);
*/
return size;
}
public byte[] get(int maxLength, boolean separator) {
byte[] byteValue = null;
if (getBitInBitmap(this.bitmapBytes, this.bitmapIndex, this.bitmapSize)) {
if (separator) {
int index = ByteUtils.indexOf(this.buffer.array(), this.separatorByte, this.buffer.position() + 1, maxLength);
if (index > 0) {
byteValue = new byte[index - this.buffer.position()];
this.buffer.get(byteValue);
this.buffer.position(this.buffer.position() + 1); // Skip separatorByte
} else {
throw new CopyBookException("Could not find expected separator in response at index " + buffer.position());
}
} else {
byteValue = new byte[maxLength];
this.buffer.get(byteValue);
}
}
if(separator) {
bitmapIndex++;
}
return byteValue;
}
public void put(byte [] bytes, boolean separator) {
if(bytes != null) {
if (ByteUtils.indexOf(bytes, this.separatorByte, 0, bytes.length) > -1) {
throw new CopyBookException("Bytes contains the separator char");
}
buffer.put(bytes);
if(debug) { System.out.print("'" + new String(bytes, StandardCharsets.UTF_8) +"'"); }
if(separator) {
if(debug) { System.out.println("[]"); }
setBitInBitmap();
this.maxUsedBit = bitmapIndex;
buffer.put(this.separatorByte);
}
}
if(separator) {
if(debug) { System.out.println("(("); }
this.bitmapIndex++;
if(debug) { System.out.println(debugBitmap(this.bitmapBytes, 0, 8)); }
}
}
public byte[] array() {
// Trim unused bytes before returning the array
int bitmapBlocks = this.maxUsedBit / (this.bitmapBlockSize * 8 - 1) + 1;
int bitmapSize = bitmapBlocks * this.bitmapBlockSize;
// Set bit 64(bit index 63) to 1
for(int i = 0; i < bitmapSize / this.bitmapBlockSize - 1; ++i) {
this.bitmapBytes[i * this.bitmapBlockSize + (this.bitmapBlockSize - 1)] = (byte)(this.bitmapBytes[i * this.bitmapBlockSize + (this.bitmapBlockSize - 1)] | 1); // 1 decimals = 00000001 binary
}
byte[] result = new byte[buffer.position() + bitmapSize];
System.arraycopy(bitmapBytes, 0, result, 0, bitmapSize); // Copy bitmap to result array
System.arraycopy(buffer.array(), 0, result, bitmapSize, buffer.position());
return result;
}
public void incBitmapIndex() {
this.bitmapIndex += 1;
}
public void incBitmapIndex(int i) {
this.bitmapIndex += i;
}
// Sets 63 bits(bit index 0-62) in 8 bytes N times where bit 64(bit index 63) tells if a new 8 bytes block is used
private void setBitInBitmap() {
int bitOffset = this.bitmapIndex / (this.bitmapBlockSize * 8 - 1) + this.bitmapIndex;
this.bitmapBytes[bitOffset / this.bitmapBlockSize] = (byte)(this.bitmapBytes[bitOffset / this.bitmapBlockSize] | (128 >> (bitOffset % 8))); // 128 decimals = 10000000 binary
}
private boolean getBitInBitmap(byte[] bytes, int bitIndex, int bitmapSize) {
bitIndex += bitIndex / 63;
if(bitIndex < bitmapSize * 8) {
return (bytes[bitIndex / this.bitmapBlockSize] & (128 >> (bitIndex % this.bitmapBlockSize))) != 0; // 128 decimals = 10000000 binary
} else {
return false;
}
}
private int getBitMapSize(byte[] data) {
// Read bit map size by comparing the last bit in each block
int e;
for(e = 1; (data[e * 8 - 1] & 1) != 0; ++e) { }
int bitMapSize = e * this.bitmapBlockSize;
if(bitMapSize > this.bitmapMaxSize) {
throw new CopyBookException("Bitmap is to large for this copybook");
}
return bitMapSize;
}
private String debugBitmap(byte[] bytes, int index, int length) {
String result = "";
for(int i = index; i < length; i++) {
result += ("0000000" + Integer.toBinaryString(bytes[i] & 0xFF)).replaceAll(".*(.{8})$", "$1");
}
return result;
}
}
}