Module id.kineticstreamer
It parses object tree and allows to get control over how types are going to be (de)serialized.
Why not Java standard ObjectOutputStream
By default ObjectOutputStream
adds to stream ObjectStreamConstants.TC_BLOCKDATA
:
var fos = new FileOutputStream("t.tmp");
var oos = new ObjectOutputStream(fos) {
@Override
protected void writeStreamHeader() throws java.io.IOException {
// ignore header
}
};
oos.writeInt(0xffff);
oos.close();
Will put to stream:
TC_BLOCKDATA|0x0000ffff
It is possible to use ObjectOutputStream default constructor to a avoid this but it requires to reimplement writeObject which does all work with class tree serialization (behind private method writeObject0 which you cannot access from writeObject because it is final).
Stream
In terms of kineticstreamer 'stream' represents sequence of any sort (sequence of bytes, words, numbers, etc). The type of elements in such sequence don't need to be the same. Streams have flat schemas opposite to JSON, XML, etc.
Example of streams are flat byte formats, CSV files, etc.
Kinetic stream
Kinetic stream is an abstraction which is defined by two interfaces:
kineticstreamer relies on them for Java objects (de)serialization.
Kinetic streams allow kineticstreamer to operate with any type of streams without even knowing anything about their format.
Every type of kinetic stream must have its own implementation of InputKineticStream
(for reading objects from it) and OutputKineticStream
for writing them).
kineticstreamer comes with some predefined kinetic streams (see id.kineticstreamer.streams
). To add support for custom types of streams you need to implement
kinetic stream interfaces for them.
Streamed classes
Streamed classes are classes which objects can be (de)serialized. These are also know as data transfer objects (DTO)
Streamed fields - fields of a streamed classes which are going to be (de)serialized.
Field types
kineticstreamer divides all types into two categories:- kinetic stream types - these are all Java primitive types plus any other types for which
there are separate method definition in
InputKineticStream
andOutputKineticStream
- foreign types - all others types (mostly these are classes which are composed from fields of kinetic stream types or other foreign types).
Understanding this separation is important for creating streamed classes and controllers.
Defining streamed classes
Streamed classes must satisfy following requirements:
- Streamed class must have default constructor
- Streamed fields of foreign types must not be null (unless you implement controllers and
handle (de)serialization of such types manually). This is required for deserialization
purposes. By default during deserialization
KineticStreamReader
expects all streamed fields of foreign types be present in the stream. It is a good practice to initialize such fields with their default constructor.
KineticStreamController
Controller is responsible for finding all streamed fields of a class and deciding in which
order they must be (de)serialized (see StreamedFieldsProvider
). By
default KineticStreamController
is using PublicStreamedFieldsProvider
which considers all public fields of a class as
streamed fields. Any static, private, final, transient fields are ignored. Users can change this
behavior by implementing their own StreamedFieldsProvider
.
Additionally controller helps in case of complex serialization logic of certain foreign field types. For example:
- it allows to support polymorphic fields which type is undefined until certain flags are read from the stream
- when certain fields may be present in the stream or not based on data which was previously read from it
Users can extend KineticStreamController
to provide custom logic
and inject into KineticStreamReader
and KineticStreamWriter
correspondingly.
Examples
Streamed class:
public class StringMessage {
public String data;
// streamed classes require default ctor
public StringMessage() {
}
public StringMessage(String data) {
this.data = data;
}
}
Streaming StringMessage object to stream of bytes and back:
// write
var tmpFile = Files.createTempFile("", "kineticstream");
var fos = new FileOutputStream(tmpFile.toFile());
var dos = new ByteOutputKineticStream(new DataOutputStream(fos));
var ksw = new KineticStreamWriter(dos);
ksw.write(new StringMessage("hello kineticstreamer"));
// read back
var fis = new FileInputStream(tmpFile.toFile());
var dis = new ByteInputKineticStream(new DataInputStream(fis));
var ksr = new KineticStreamReader(dis);
StringMessage actual = (StringMessage) ksr.read(StringMessage.class);
System.out.println(actual.data);
Java records support
Currently kineticstreamer does not support records (de)serialization.
The reason behind it is based on observation that users are not using records as streamed classes (DTO).
Here is an example of a streamed class definition MarkerMessage which is used to communicate with Robot Operating System.
This class has certain fields which need to be set only in certain cases, otherwise they should contain default values:
- text - Only used for text markers
- mesh_resource - Only used for MESH_RESOURCE markers
Additionally some fields are assigned default values, so they don't need to be populated by the user each time.
- lifetime - default is forever
To convert MarkerMessage to record and not loose all the specifics given above there are two ways:
- define multiple overloaded constructors for all possible ways MarkerMessage can be created,
like:
- MarkerMessage(..., Duration lifetime) - user provided duration
- MarkerMessage(...) - default duration
- ...
- define a separate builder which will look like current MarkerMessage except at the end it will produce record version of MarkerMessage. This is again time consuming and error prone.
To summarize: possibly one reason for records not being widely used as DTO is because they lack any builders support.
Useful links:
- See Also:
-
Packages
PackageDescriptionCollection of default kinetic stream implementations.