|
Basics
Basics To create and use a serializer: final ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
final Serializer serializer = GraphSerializerFactory.serializer(Graph.class);
final Graph original = build();
serializer.write(buffer, original);
buffer.flip();
final Graph recovered = (Graph) serializer.read(buffer);
//test for equality
Assert.assertNotSame(recovered, original);Serializers Layout Serializers created via GraphSerializerFactory usually will inherit from the abstract class GraphSerializer
public abstract class GraphSerializer implements Serializer {
public abstract void doRead(ByteBuffer src, Object obj);
public abstract void doWrite(ByteBuffer dest, Object obj);
public abstract Object instantiate(ByteBuffer src);
}
The 3 abstract methods will be generated via asm, and are intended to read the object's payload created beforehand via instantiate. The write method takes an in memory object and writes it's payload. For instance, consider the following 3 classes: public class Graph {
List<Edge> edges = new ArrayList<Edge>(4);
public void add(final Edge edge) {
edges.add(edge);
edge.owner = this;
}
//hc and equals ommited (not actually used by the serializer)
public void test() {
Assert.assertTrue(edges.size() > 0);
for (final Edge edge : edges) {
edge.test(this);
}
}
}
public class Edge {
int id;
Graph owner;
Vertex src;
Vertex dest;
public void dest(final Vertex dest) {
this.dest = dest;
dest.edges.add(this);
}
//hc and equals ommited (not actually used by the serializer)
public void src(final Vertex src) {
this.src = src;
src.edges.add(this);
}
public void test(final Graph graph) {
Assert.assertSame(owner, graph);
src.test(this);
dest.test(this);
}
}
public class Vertex {
int id;
List<Edge> edges = new ArrayList<Edge>(1);
public void test(final Edge edge) {
for (final Edge e : edges) {
if (e == edge) {
return;
}
}
Assert.fail("Edge not found!");
}
}
The code generator will inspect them via reflection and generate the following classes:
public final class Graph_$GRAPH_SER extends GraphSerializer
{
public final void doRead(ByteBuffer src, Object o)
{
Graph g = (Graph)o;
int i = Representation.readNullFlags(src);
g.edges = ((List)GraphSerializerFactory.readNested(src, i, 0));
}
public final void doWrite(ByteBuffer dest, Object o)
{
Graph g = (Graph)o;
int i = Representation.writeNullFlags(dest, new Object[] { g.edges });
GraphSerializerFactory.writeNested(dest, g.edges, i, 0);
}
public Object instantiate(ByteBuffer src)
{
return new Graph();
}
}
public final class Edge_$GRAPH_SER extends GraphSerializer
{
public final void doRead(ByteBuffer src, Object o)
{
Edge e = (Edge)o;
e.id = Representation.readInt(src);
int i = Representation.readNullFlags(src);
e.owner = ((Graph)GraphSerializerFactory.readNested(src, i, 0));
e.src = ((Vertex)GraphSerializerFactory.readNested(src, i, 1));
e.dest = ((Vertex)GraphSerializerFactory.readNested(src, i, 2));
}
public final void doWrite(ByteBuffer dest, Object o)
{
Edge e = (Edge)o;
Representation.writeInt(dest, e.id);
int i = Representation.writeNullFlags(dest, new Object[] { e.owner, e.src, e.dest });
GraphSerializerFactory.writeNested(dest, e.owner, i, 0);
GraphSerializerFactory.writeNested(dest, e.src, i, 1);
GraphSerializerFactory.writeNested(dest, e.dest, i, 2);
}
public Object instantiate(ByteBuffer paramByteBuffer)
{
return new Edge();
}
}
public final class Vertex_$GRAPH_SER extends GraphSerializer
{
public final void doRead(ByteBuffer src, Object o)
{
Vertex v = (Vertex)o;
v.id = Representation.readInt(src);
int i = Representation.readNullFlags(src);
v.edges = ((List)GraphSerializerFactory.readNested(src, i, 0));
}
public final void doWrite(ByteBuffer dest, Object o)
{
Vertex v = (Vertex)paramObject;
Representation.writeInt(dest, v.id);
int i = Representation.writeNullFlags(dest, new Object[] { v.edges });
GraphSerializerFactory.writeNested(dest, v.edges, i, 0);
}
public Object instantiate(ByteBuffer paramByteBuffer)
{
return new Vertex();
}
}
Whenever possible, serializers are created to use the same ClassLoader of their mirror classes so that access to their corresponding fields can be done up to "standard" modifier. By default private fields are ignored, unless the target class is customized via the @Serializable annotation. To serialize a final or private field, the serializers will use PrivateAccessor, which currently delegates to sun.misc.Unsafe. Primitives, References and Embedded Bit mask Primitive fields and their corresponding wrappers will be written "inline" in the target buffer. For the wrappers and any objects, however, we must check for non-null. The check is done by calling Representation.writeNullFlags, which will embed a bitset in the buffer for selecting non-null values. The Edge class, for instance, has 4 fields, 3 being references. Thus: int i = Representation.writeNullFlags(dest, new Object[] { e.owner, e.src, e.dest });will return a bit-set like 111 if none are null or 101 for an Edge without a source Vertex. References must be checked for both nullability and ciclicity, delegating the call to GraphSerializerFactory. The calls GraphSerializerFactory.writeNested(dest, o, i, ix); GraphSerializerFactory.readNested(src, i, ix)); Will consult the bit map ( (i & (1<<ix))!=0) to check if the objects are not null before actually performing any read/write operation. |