package org.djutils.serialization; import java.io.IOException; import org.djutils.decoderdumper.Decoder; /** * Decoder for inspection of serialized data. *
* Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version Jan 3, 2019
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
public class SerialDataDecoder implements Decoder
{
/** The endian util to use to decode multi-byte values. */
private final EndianUtil endianUtil;
/** Type of the data that is currently being decoded. */
private byte currentFieldType;
/** The serializer for the currentFieldType
. */
private Serializer> currentSerializer = null;
/** Position in the data of the currentFieldType
. */
private int positionInData = -1;
/** Position in the dataElementBytes where the next input byte shall be store. */
private int nextDataElementByte = -1;
/** Collects the bytes that constitute the current data element. */
private byte[] dataElementBytes = new byte[0];
/** Size of the data that is currently being decode. */
private int totalDataSize = -1;
/** Number of rows in an array or matrix. */
private int rowCount;
/** Number of columns in a matrix. */
private int columnCount;
/** Row of matrix or array that we are now reading. */
private int currentRow;
/** Column of matrix that we are now reading. */
private int currentColumn;
/** String builder for current output line. */
private StringBuilder buffer = new StringBuilder();
/**
* Construct a new SerialDataDecoder.
* @param endianUtil EndianUtil; the endian util to use to decode multi-byte values
*/
SerialDataDecoder(final EndianUtil endianUtil)
{
this.endianUtil = endianUtil;
}
@Override
public final String getResult()
{
String result = this.buffer.toString();
this.buffer.setLength(0);
return result;
}
@Override
public final int getMaximumWidth()
{
return 40;
}
@Override
public final boolean append(final int address, final byte theByte) throws IOException
{
boolean result = false;
if (null == this.currentSerializer)
{
// We are expecting a field type byte
this.currentFieldType = theByte;
this.currentSerializer = TypedMessage.PRIMITIVE_DATA_DECODERS.get(this.currentFieldType);
if (null == this.currentSerializer)
{
this.buffer.append(String.format("Bad field type %02x - resynchronizing", this.currentFieldType));
result = true;
// May eventually re-synchronize, but that could take a lot of data.
}
else
{
this.positionInData = 1;
this.totalDataSize = 1; // to be adjusted
if (this.currentSerializer instanceof ObjectSerializer)
{
// TODO handle the display unit si unit and money unit
}
else
{
try
{
if (this.currentSerializer.getNumberOfDimensions() == 0)
{
int size = this.currentSerializer.size(null);
this.totalDataSize += size;
prepareForDataElement(size);
}
else
{
int size = this.currentSerializer.getNumberOfDimensions() * 4;
prepareForDataElement(size);
this.totalDataSize += size;
}
}
catch (SerializationException e)
{
e.printStackTrace(); // Cannot happen
}
}
}
return result;
}
if (this.nextDataElementByte < this.dataElementBytes.length)
{
this.dataElementBytes[this.nextDataElementByte] = theByte;
}
this.nextDataElementByte++;
this.positionInData++;
if (this.nextDataElementByte == this.dataElementBytes.length)
{
if (this.currentSerializer instanceof FixedSizeObjectSerializer)
{
try
{
Object value = this.currentSerializer.deSerialize(this.dataElementBytes, new Pointer(), this.endianUtil);
buffer.append(value.toString());
}
catch (SerializationException e)
{
buffer.append("Error deserializing data");
}
}
else if (this.currentSerializer.getNumberOfDimensions() > 0)
{
if (this.rowCount == 0)
{
// Got the height and width of a matrix, or length of an array
this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
this.currentRow = 0;
this.currentColumn = 0;
if (this.dataElementBytes.length == 8)
{
this.rowCount = this.columnCount;
this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
this.buffer.append(String.format("%s height %d, width %d", this.currentSerializer.dataClassName(),
this.rowCount, this.columnCount));
}
else
{
this.rowCount = 1;
this.buffer
.append(String.format("%s length %d", this.currentSerializer.dataClassName(), this.columnCount));
}
int elementSize = -1;
if (this.currentSerializer instanceof ArrayOrMatrixSerializer, ?>)
{
elementSize = ((ArrayOrMatrixSerializer, ?>) this.currentSerializer).getElementSize();
}
else if (this.currentSerializer instanceof BasicPrimitiveArrayOrMatrixSerializer)
{
elementSize = ((BasicPrimitiveArrayOrMatrixSerializer>) this.currentSerializer).getElementSize();
}
else
{
throw new RuntimeException("Unhandled type of array or matrix serializer");
}
this.totalDataSize += elementSize * this.rowCount * this.columnCount;
prepareForDataElement(elementSize);
// System.out.println("Selecting element size " + elementSize + " for serializer "
// + this.currentSerializer.dataClassName());
}
else
{
// Got one data element
if (this.currentSerializer.getNumberOfDimensions() == 1)
{
this.buffer.append(String.format("value at index %d: ", this.currentColumn));
}
else // it is 2
{
this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
}
if (this.currentSerializer instanceof ArrayOrMatrixSerializer, ?>)
{
Object value = ((ArrayOrMatrixSerializer, ?>) this.currentSerializer)
.deSerializeElement(dataElementBytes, 0, this.endianUtil);
this.buffer.append(value.toString());
}
else if (this.currentSerializer instanceof BasicPrimitiveArrayOrMatrixSerializer)
{
// It looks like we'll have to do this ourselves.
BasicPrimitiveArrayOrMatrixSerializer> basicPrimitiveArraySerializer =
(BasicPrimitiveArrayOrMatrixSerializer>) this.currentSerializer;
switch (basicPrimitiveArraySerializer.fieldType())
{
case FieldTypes.BYTE_8_ARRAY:
case FieldTypes.BYTE_8_MATRIX:
this.buffer.append(String.format("%02x", dataElementBytes[0]));
break;
case FieldTypes.SHORT_16_ARRAY:
case FieldTypes.SHORT_16_MATRIX:
this.buffer.append(String.format("%d", endianUtil.decodeShort(dataElementBytes, 0)));
break;
case FieldTypes.INT_32_ARRAY:
case FieldTypes.INT_32_MATRIX:
this.buffer.append(String.format("%d", endianUtil.decodeInt(dataElementBytes, 0)));
break;
case FieldTypes.LONG_64_ARRAY:
case FieldTypes.LONG_64_MATRIX:
this.buffer.append(String.format("%d", endianUtil.decodeLong(dataElementBytes, 0)));
break;
case FieldTypes.FLOAT_32_ARRAY:
case FieldTypes.FLOAT_32_MATRIX:
this.buffer.append(String.format("%f", endianUtil.decodeFloat(dataElementBytes, 0)));
break;
case FieldTypes.DOUBLE_64_ARRAY:
case FieldTypes.DOUBLE_64_MATRIX:
this.buffer.append(String.format("%f", endianUtil.decodeDouble(dataElementBytes, 0)));
break;
case FieldTypes.BOOLEAN_8_ARRAY:
case FieldTypes.BOOLEAN_8_MATRIX:
this.buffer.append(0 == dataElementBytes[0] ? "false" : "true");
break;
default:
throw new RuntimeException(
"Unhandled type of basicPrimitiveArraySerializer: " + basicPrimitiveArraySerializer);
}
}
this.nextDataElementByte = 0;
this.currentColumn++;
if (this.currentColumn == this.columnCount)
{
this.currentColumn = 0;
this.currentRow++;
}
}
// System.out.println(
// "Parsed 1 element; next element is for column " + this.currentColumn + ", row " + this.currentRow);
result = true;
}
}
if (this.positionInData == this.totalDataSize)
{
this.currentSerializer = null;
this.positionInData = -1;
this.totalDataSize = -1;
this.rowCount = 0;
this.columnCount = 0;
return true;
}
return result;
}
/**
* Allocate a buffer for the next data element (or two).
* @param dataElementSize int; size of the buffer
*/
private void prepareForDataElement(final int dataElementSize)
{
this.dataElementBytes = new byte[dataElementSize];
this.nextDataElementByte = 0;
}
@Override
public final boolean ignoreForIdenticalOutputCheck()
{
return false;
}
}