package com.github.davidmoten.rtree.fbs; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import com.github.davidmoten.guavamini.Optional; import com.github.davidmoten.guavamini.annotations.VisibleForTesting; import com.github.davidmoten.rtree.Context; import com.github.davidmoten.rtree.Entry; import com.github.davidmoten.rtree.InternalStructure; import com.github.davidmoten.rtree.Leaf; import com.github.davidmoten.rtree.Node; import com.github.davidmoten.rtree.NonLeaf; import com.github.davidmoten.rtree.RTree; import com.github.davidmoten.rtree.SelectorRStar; import com.github.davidmoten.rtree.Serializer; import com.github.davidmoten.rtree.SerializerHelper; import com.github.davidmoten.rtree.SplitterRStar; import com.github.davidmoten.rtree.fbs.generated.Box_; import com.github.davidmoten.rtree.fbs.generated.Context_; import com.github.davidmoten.rtree.fbs.generated.Node_; import com.github.davidmoten.rtree.fbs.generated.Tree_; import com.github.davidmoten.rtree.geometry.Geometries; import com.github.davidmoten.rtree.geometry.Geometry; import com.github.davidmoten.rtree.geometry.Rectangle; import com.github.davidmoten.rtree.internal.LeafDefault; import com.github.davidmoten.rtree.internal.NonLeafDefault; import com.google.flatbuffers.FlatBufferBuilder; import rx.functions.Func1; public final class SerializerFlatBuffers<T, S extends Geometry> implements Serializer<T, S> { private final FactoryFlatBuffers<T, S> factory; private SerializerFlatBuffers(Func1<? super T, byte[]> serializer, Func1<byte[], ? extends T> deserializer) { this.factory = new FactoryFlatBuffers<T, S>(serializer, deserializer); } public static <T, S extends Geometry> Serializer<T, S> create( Func1<? super T, byte[]> serializer, Func1<byte[], ? extends T> deserializer) { return new SerializerFlatBuffers<T, S>(serializer, deserializer); } /* * (non-Javadoc) * * @see com.github.davidmoten.rtree.fbs.Serializer#serialize(com.github. * davidmoten.rtree.RTree, java.io.OutputStream) */ @Override public void write(RTree<T, S> tree, OutputStream os) throws IOException { FlatBufferBuilder builder = new FlatBufferBuilder(); final Rectangle mbb; if (tree.root().isPresent()) { mbb = tree.root().get().geometry().mbr(); } else { mbb = Geometries.rectangle(0, 0, 0, 0); } int b = Box_.createBox_(builder, mbb.x1(), mbb.y1(), mbb.x2(), mbb.y2()); Context_.startContext_(builder); Context_.addBounds(builder, b); Context_.addMinChildren(builder, tree.context().minChildren()); Context_.addMaxChildren(builder, tree.context().maxChildren()); int c = Context_.endContext_(builder); final int n; if (tree.root().isPresent()) { n = addNode(tree.root().get(), builder, factory.serializer()); } else { // won't be used n = 0; } // int t = Tree_.createTree_(builder, c, n, tree.size()); Tree_.startTree_(builder); Tree_.addContext(builder, c); Tree_.addSize(builder, tree.size()); if (tree.size() > 0) { Tree_.addRoot(builder, n); } int t = Tree_.endTree_(builder); Tree_.finishTree_Buffer(builder, t); ByteBuffer bb = builder.dataBuffer(); os.write(bb.array(), bb.position(), bb.remaining()); } private static <T, S extends Geometry> int addNode(Node<T, S> node, FlatBufferBuilder builder, Func1<? super T, byte[]> serializer) { if (node instanceof Leaf) { Leaf<T, S> leaf = (Leaf<T, S>) node; return FlatBuffersHelper.addEntries(leaf.entries(), builder, serializer); } else { NonLeaf<T, S> nonLeaf = (NonLeaf<T, S>) node; int[] nodes = new int[nonLeaf.count()]; for (int i = 0; i < nonLeaf.count(); i++) { Node<T, S> child = nonLeaf.child(i); nodes[i] = addNode(child, builder, serializer); } int ch = Node_.createChildrenVector(builder, nodes); Node_.startNode_(builder); Node_.addChildren(builder, ch); Rectangle mbb = nonLeaf.geometry().mbr(); int b = Box_.createBox_(builder, mbb.x1(), mbb.y1(), mbb.x2(), mbb.y2()); Node_.addMbb(builder, b); return Node_.endNode_(builder); } } /* * (non-Javadoc) * * @see com.github.davidmoten.rtree.fbs.Serializer#deserialize(long, * java.io.InputStream, com.github.davidmoten.rtree.InternalStructure) */ @Override public RTree<T, S> read(InputStream is, long sizeBytes, InternalStructure structure) throws IOException { byte[] bytes = readFully(is, (int) sizeBytes); Tree_ t = Tree_.getRootAsTree_(ByteBuffer.wrap(bytes)); Context<T, S> context = new Context<T, S>(t.context().minChildren(), t.context().maxChildren(), new SelectorRStar(), new SplitterRStar(), factory); Node_ node = t.root(); if (node == null) { return SerializerHelper.create(Optional.<Node<T, S>> absent(), 0, context); } else { final Node<T, S> root; if (structure == InternalStructure.SINGLE_ARRAY) { if (node.childrenLength() > 0) { root = new NonLeafFlatBuffers<T, S>(node, context, factory.deserializer()); } else { root = new LeafFlatBuffers<T, S>(node, context, factory.deserializer()); } } else { root = toNodeDefault(node, context, factory.deserializer()); } return SerializerHelper.create(Optional.of(root), (int) t.size(), context); } } private static <T, S extends Geometry> Node<T, S> toNodeDefault(Node_ node, Context<T, S> context, Func1<byte[], ? extends T> deserializer) { int numChildren = node.childrenLength(); if (numChildren > 0) { List<Node<T, S>> children = new ArrayList<Node<T, S>>(numChildren); for (int i = 0; i < numChildren; i++) { children.add(toNodeDefault(node.children(i), context, deserializer)); } return new NonLeafDefault<T, S>(children, context); } else { List<Entry<T, S>> entries = FlatBuffersHelper.createEntries(node, deserializer); return new LeafDefault<T, S>(entries, context); } } @VisibleForTesting static byte[] readFully(InputStream is, int numBytes) throws IOException { byte[] b = new byte[numBytes]; int count = 0; do { int n = is.read(b, count, numBytes - count); if (n > 0) { count += n; } else { throw new RuntimeException("unexpected"); } } while (count < numBytes); return b; } }