SerializeUtil.java
package zserio.runtime.io;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import zserio.runtime.ZserioEnum;
import zserio.runtime.ZserioError;
/**
* Provides help methods for serialization and deserialization of generated objects.
* <p>
* These utilities are not used by generated code and they are provided only for user convenience.
*/
public final class SerializeUtil
{
/**
* Serializes generated object to the bit buffer.
* <p>
* Before serialization, the method calls initializeOffsets() on the given zserio object.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* final BitBuffer bitBuffer = SerializeUtil.serialize(object);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param object Generated object to serialize.
*
* @return Bit buffer which represents generated object in binary format.
*/
public static <T extends Writer> BitBuffer serialize(T object)
{
try (final ByteArrayBitStreamWriter writer = new ByteArrayBitStreamWriter())
{
serializeToWriter(object, writer);
return new BitBuffer(writer.toByteArray(), writer.getBitPosition());
}
catch (IOException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
/**
* Deserializes bit buffer to the generated object.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* final BitBuffer bitBuffer = SerializeUtil.serialize(object);
* final SomeZserioObject readObject = SerializeUtil.deserialize(SomeZserioObject.class, bitBuffer);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param clazz Class instance of the generated object to deserialize.
* @param bitBuffer Bit buffer which represents generated object in binary format.
* @param arguments Additional arguments needed for reader constructor (optional).
*
* @return Generated object created from given bit buffer.
*/
public static <T> T deserialize(Class<T> clazz, BitBuffer bitBuffer, Object... arguments)
{
return deserializeFromBytes(clazz, bitBuffer.getBuffer(), arguments);
}
/**
* Serializes generated object to the byte array.
* <p>
* Before serialization, the method calls initializeOffsets() on the given zserio object.
* <p>
* This is a convenient method for users which do not need exact number of bits to which the given object
* will be serialized.
* <p>
* However, it's still possible that not all bits of the last byte are used. In this case, only most
* significant bits of the corresponding size are used.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* final byte[] buffer = SerializeUtil.serializeToBytes(object);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param object Generated object to serialize.
*
* @return Byte array which represents generated object in binary format.
*/
public static <T extends Writer> byte[] serializeToBytes(T object)
{
try (final ByteArrayBitStreamWriter writer = new ByteArrayBitStreamWriter())
{
serializeToWriter(object, writer);
return writer.toByteArray();
}
catch (IOException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
/**
* Deserializes byte array to the generated object.
* <p>
* This method can potentially use all bits of the last byte even if not all of them were written during
* serialization (because there is no way how to specify exact number of bits). Thus, it could allow reading
* behind stream (possibly in case of damaged data).
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* final byte[] buffer = SerializeUtil.serializeToBytes(object);
* final SomeZserioObject readObject = SerializeUtil.deserializeFromBytes(SomeZserioObject.class, buffer);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param clazz Class instance of the generated object to deserialize.
* @param buffer Byte array which represents generated object in binary format.
* @param arguments Additional arguments needed for reader constructor (optional).
*
* @return Generated object created from given byte array.
*/
public static <T> T deserializeFromBytes(Class<T> clazz, byte[] buffer, Object... arguments)
{
try (final BitStreamReader reader = new ByteArrayBitStreamReader(buffer))
{
return deserializeFromReader(clazz, reader, arguments);
}
catch (IOException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
/**
* Serializes generated object to the file using file name.
* <p>
* Before serialization, the method calls initializeOffsets() on the given zserio object.
* <p>
* This is a convenient method for users to easily write given generated object to file.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* SerializeUtil.serializeToFile(object, "FileName.bin");
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param object Generated object to serialize.
* @param fileName Name of the file to write.
*/
public static <T extends Writer> void serializeToFile(T object, String fileName)
{
serializeToFile(object, new File(fileName));
}
/**
* Serializes generated object to the file.
* <p>
* Before serialization, the method calls initializeOffsets() on the given zserio object.
* <p>
* This is a convenient method for users to easily write given generated object to file.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final SomeZserioObject object = new SomeZserioObject();
* SerializeUtil.serializeToFile(object, "FileName.bin");
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param object Generated object to serialize.
* @param file File to write.
*/
public static <T extends Writer> void serializeToFile(T object, File file)
{
try (final ByteArrayBitStreamWriter writer = new ByteArrayBitStreamWriter())
{
serializeToWriter(object, writer);
final byte[] bytes = writer.toByteArray();
try (final FileOutputStream outputStream = new FileOutputStream(file))
{
outputStream.write(bytes);
outputStream.flush();
}
}
catch (IOException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
/**
* Deserializes file to the generated object using file name.
* <p>
* This is a convenient method for users to easily read given generated object from file.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final String fileName = "FileName.bin";
* final SomeZserioObject object = new SomeZserioObject();
* SerializeUtil.serializeToFile(object, fileName);
* final SomeZserioObject readObject = SerializeUtil.deserializeFromFile(SomeZserioObject.class, fileName);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param clazz Class instance of the generated object to deserialize.
* @param fileName Name of the file which represents generated object in binary format.
* @param arguments Additional arguments needed for reader constructor (optional).
*
* @return Generated object created from given file contents.
*/
public static <T> T deserializeFromFile(Class<T> clazz, String fileName, Object... arguments)
{
return deserializeFromFile(clazz, new File(fileName), arguments);
}
/**
* Deserializes file to the generated object.
* <p>
* This is a convenient method for users to easily read given generated object from file.
* <p>
* Example:
* <blockquote><pre>
* import zserio.runtime.io.SerializeUtil;
*
* final String fileName = "FileName.bin";
* final SomeZserioObject object = new SomeZserioObject();
* SerializeUtil.serializeToFile(object, fileName);
* final SomeZserioObject readObject = SerializeUtil.deserializeFromFile(SomeZserioObject.class, fileName);
* </pre></blockquote>
*
* @param <T> Type of generated object.
* @param clazz Class instance of the generated object to deserialize.
* @param file File which represents generated object in binary format.
* @param arguments Additional arguments needed for reader constructor (optional).
*
* @return Generated object created from given file contents.
*/
public static <T> T deserializeFromFile(Class<T> clazz, File file, Object... arguments)
{
try
{
final byte[] fileContent = Files.readAllBytes(file.toPath());
try (final BitStreamReader reader = new ByteArrayBitStreamReader(fileContent))
{
return deserializeFromReader(clazz, reader, arguments);
}
}
catch (IOException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
private static <T extends Writer> void serializeToWriter(T object, BitStreamWriter writer)
throws IOException
{
object.initializeOffsets(writer.getBitPosition());
object.write(writer);
}
private static <T> T deserializeFromReader(Class<T> clazz, BitStreamReader reader, Object... arguments)
{
try
{
if (Arrays.asList(clazz.getInterfaces()).contains(ZserioEnum.class))
{
final Method method = clazz.getMethod("readEnum", BitStreamReader.class);
return clazz.cast(method.invoke(null, reader));
}
else
{
final Class<?>[] ctorArgumentTypes = new Class<?>[arguments.length + 1];
ctorArgumentTypes[0] = BitStreamReader.class;
// please note that arguments are always boxed and object parameters are always unboxed
for (int i = 0; i < arguments.length; ++i)
ctorArgumentTypes[i + 1] = toUnboxedClass(arguments[i].getClass());
final Constructor<T> constructor = clazz.getConstructor(ctorArgumentTypes);
final Object[] ctorArguments = new Object[arguments.length + 1];
ctorArguments[0] = reader;
for (int i = 0; i < arguments.length; ++i)
ctorArguments[i + 1] = arguments[i];
return constructor.newInstance(ctorArguments);
}
}
catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException exception)
{
throw new ZserioError("SerializeUtil: " + exception, exception);
}
}
private static Class<?> toUnboxedClass(Class<?> clazz)
{
final Class<?> unboxedClazz = boxedToUnboxedClassMap.get(clazz);
return (unboxedClazz == null) ? clazz : unboxedClazz;
}
private static final Map<Class<?>, Class<?>> boxedToUnboxedClassMap = new HashMap<Class<?>, Class<?>>();
static
{
boxedToUnboxedClassMap.put(Boolean.class, boolean.class);
boxedToUnboxedClassMap.put(Byte.class, byte.class);
boxedToUnboxedClassMap.put(Short.class, short.class);
boxedToUnboxedClassMap.put(Character.class, char.class);
boxedToUnboxedClassMap.put(Integer.class, int.class);
boxedToUnboxedClassMap.put(Long.class, long.class);
boxedToUnboxedClassMap.put(Float.class, float.class);
boxedToUnboxedClassMap.put(Double.class, double.class);
}
}