1 /*
2  * Copyright 2016 Google Inc. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16  
17 module flatbuffers.flatbufferbuilder;
18 
19 import flatbuffers.exception;
20 import flatbuffers.bytebuffer;
21 
22 import std.exception;
23 import std.traits : isNumeric;
24 
25 /**
26     Responsible for building up and accessing a FlatBuffer formatted byte
27 */
28 final class FlatBufferBuilder
29 {
30     /**
31         Create a FlatBufferBuilder with a given initial size.
32         Params:
33             initsize = The initial size to use for the internal buffer.
34     */
35     this(size_t initsize = 32)
36 	in{
37 		assert(initsize > 0);
38 	}body{
39 		this(new ByteBuffer(new ubyte[initsize]));
40     }
41 
42 	this(ByteBuffer buffer){
43 		_space = buffer.length;
44 		_buffer = buffer;
45 	}
46 
47     uint offset()
48     {
49         return cast(uint)(_buffer.length - _space);
50     }
51 
52     void pad(size_t size)
53     {
54         for (int i = 0; i < size; i++)
55         {
56             --_space;
57             _buffer.put!ubyte(_space, 0x00);
58         }
59     }
60 
61     /** Doubles the size of the ByteBuffer, and copies the old data towards
62         the end of the new buffer (since we build the buffer backwards).
63     */
64     void growBuffer()
65     {
66         auto oldBuf = _buffer.data;
67         auto oldBufSize = oldBuf.length;
68         if ((oldBufSize & 0xC0000000) != 0)
69             throw new Exception("FlatBuffers: cannot grow buffer beyond 2 gigabytes.");
70 
71 		auto newBufSize = oldBufSize >= 32 ? oldBufSize * 2 : 64;
72         auto newBuf = new ubyte[](newBufSize);
73         newBuf[(newBufSize - oldBufSize) .. $] = oldBuf[];
74 		_buffer.restData(newBuf,0);
75     }
76 
77     /**
78         Prepare to write an element of `size` after `additional_bytes`
79         have been written, e.g. if you write a string, you need to align
80         such the int length field is aligned to SIZEOF_INT, and the string
81         data follows it directly.
82         If all you need to do is align, `additional_bytes` will be 0.
83     */
84     void prep(size_t size, size_t additionalBytes)
85     {
86         // Track the biggest thing we've ever aligned to.
87         if (size > _minAlign)
88             _minAlign = size;
89 
90         // Find the amount of alignment needed such that `size` is properly
91         // aligned after `additional_bytes`.
92         auto alignSize = ((~( _buffer.length - _space + additionalBytes)) + 1) & (size - 1);
93 
94         // Reallocate the buffer if needed.
95         while (_space < alignSize + size + additionalBytes)
96         {
97             auto oldBufSize = cast(int) _buffer.length;
98             growBuffer();
99             _space += cast(int) _buffer.length - oldBufSize;
100         }
101         if (alignSize > 0)
102             pad(alignSize);
103     }
104 
105     /**
106         put a value into the buffer.
107     */
108     void put(T)(T x) if (is(T == bool) || isNumeric!T)
109     {
110         static if (is(T == bool))
111         {
112             _space -= 1;
113         }
114         else
115         {
116             _space -= T.sizeof;
117         }
118         _buffer.put!T(_space, x);
119     }
120 
121     /// Adds a scalar to the buffer, properly aligned, and the buffer grown if needed.
122 	void add(T)(T x)if (is(T == bool) || isNumeric!T)
123     {
124 		static if (is(T == bool))
125 			prep(1, 0);
126 		else
127 			prep(T.sizeof, 0);
128         put!T(x);
129     }
130     /// Adds on offset, relative to where it will be written.
131     void addOffset(uint off)
132     {
133 		prep(uint.sizeof, 0); // Ensure alignment is already done.
134         if (off > offset())
135             throw new ArgumentException("FlatBuffers: must be less than offset.", "off");
136 
137 		off = offset() - off + cast(uint)uint.sizeof;
138         put!uint(off);
139     }
140 
141     void startVector(int elemSize, int count, int alignment)
142     {
143         notNested();
144         _vectorNumElems = count;
145         prep(int.sizeof, elemSize * count);
146         prep(alignment, elemSize * count); // Just in case alignment > int.
147     }
148 
149     uint endVector()
150     {
151         put!int(cast(int)_vectorNumElems);
152         return offset();
153     }
154 
155     void nested(int obj)
156     {
157         // Structs are always stored inline, so need to be created right
158         // where they are used. You'll get this assert if you created it
159         // elsewhere.
160         if (obj != offset())
161             throw new Exception("FlatBuffers: struct must be serialized inline.");
162     }
163 
164     void notNested()
165     {
166         // You should not be creating any other objects or strings/vectors
167         // while an object is being constructed.
168         if (_vtable)
169             throw new Exception("FlatBuffers: object serialization must not be nested.");
170     }
171 
172     void startObject(int numfields)
173     {
174         if (numfields < 0)
175             throw new ArgumentOutOfRangeException("numfields", numfields,
176                 "must be greater than zero");
177 
178         notNested();
179         _vtable = new size_t[](numfields);
180         _objectStart = offset();
181     }
182 
183     /// Set the current vtable at `voffset` to the current location in the buffer.
184 	void slot(size_t voffset)
185     {
186         _vtable[voffset] = offset();
187     }
188 
189     /// Add a scalar to a table at `o` into its vtable, with value `x` and default `d`.
190     void add(T : bool)(size_t o, T x, T d)
191     {
192         if (x != d)
193         {
194             add!T(x);
195             slot(o);
196         }
197     }
198     /// ditto
199 	void add(T)(size_t o, T x, T d) if(isNumeric!T)
200     {
201         if (x != d)
202         {
203             add!T(x);
204             slot(o);
205         }
206     }
207     /// ditto
208     void addOffset(int o, int x, int d)
209     {
210         if (x != d)
211         {
212             addOffset(x);
213             slot(o);
214         }
215     }
216 
217 	/** Structs are stored inline, so nothing additional is being added.
218         `d` is always 0.
219     */
220 	void addStruct(int voffset, int x, int d)
221 	{
222 		if (x != d)
223 		{
224 			nested(x);
225 			slot(voffset);
226 		}
227 	}
228 
229     /**
230         Encode the string `s` in the buffer using UTF-8.
231         Params:
232             s = The string to encode.
233         Returns:
234             The offset in the buffer where the encoded string starts.
235     */
236     uint createString(string s)
237     {
238         notNested();
239         auto utf8 = cast(ubyte[]) s;
240         add!ubyte(cast(ubyte) 0);
241         startVector(1, cast(int) utf8.length, 1);
242         _space -= utf8.length;
243         _buffer.data[_space .. _space + utf8.length] = utf8[];
244         return endVector();
245     }
246 
247     uint endObject()
248     {
249         if (!_vtable)
250             throw new InvalidOperationException(
251                 "Flatbuffers: calling endObject without a startObject");
252 
253         add!int(cast(int) 0);
254         auto vtableloc = offset();
255 
256         // Write out the current vtable.
257         for (int i = cast(int) _vtable.length - 1; i >= 0; i--)
258         {
259             // Offset relative to the start of the table.
260             short off = cast(short)(_vtable[i] != 0 ? vtableloc - _vtable[i] : 0);
261             add!short(off);
262         }
263 
264         const int standardFields = 2; // The fields below:
265         add!short(cast(short)(vtableloc - _objectStart));
266         add!short(cast(short)((_vtable.length + standardFields) * short.sizeof));
267 
268         /// Search for an existing vtable that matches the current one.
269         size_t existingVtable = 0;
270 
271         ubyte[] data = _buffer.data();
272 
273         for (int i = 0; i < _numVtables; i++)
274         {
275             auto vt1 = _buffer.length - _vtables[i];
276             auto vt2 = _space;
277             short vt1len = _buffer.get!short(vt1);
278             short vt2len = _buffer.get!short(vt2);
279 
280             if (vt1len != vt2len || data[vt1 .. (vt1 + vt1len)] != data[vt2 .. (vt2 + vt2len)])
281                 continue;
282             existingVtable = _vtables[i];
283         }
284 
285         if (existingVtable != 0)
286         {
287             // Found a match:
288             // Remove the current vtable.
289             _space = _buffer.length - vtableloc;
290             // Point table to existing vtable.
291             _buffer.put!int(_space, cast(int)(existingVtable - vtableloc));
292         }
293         else
294         {
295             // No match:
296             // Add the location of the current vtable to the list of vtables.
297             if (_numVtables == _vtables.length)
298                 _vtables.length *= 2;
299             _vtables[_numVtables++] = offset();
300             // Point table to current vtable.
301             _buffer.put!int(_buffer.length - vtableloc, offset() - vtableloc);
302         }
303 
304         destroy(_vtable);
305         _vtable = null;
306         return vtableloc;
307     }
308 
309     /** This checks a required field has been set in a given table that has
310         just been constructed.
311     */
312     void required(int table, int field)
313     {
314         import std.string;
315 
316         auto table_start = _buffer.length - table;
317         auto vtable_start = table_start - _buffer.get!int(table_start);
318         bool ok = _buffer.get!short(vtable_start + field) != 0;
319         // If this fails, the caller will show what field needs to be set.
320         if (!ok)
321             throw new InvalidOperationException(format("FlatBuffers: field %s must be set.",
322                 field));
323     }
324     
325     /**
326         Finalize a buffer, pointing to the given `root_table`.
327         Params:
328             rootTable = An offset to be added to the buffer.
329     */
330     void finish(int rootTable)
331     {
332         prep(_minAlign, int.sizeof);
333         addOffset(rootTable);
334     }
335 
336     /**
337         Get the ByteBuffer representing the FlatBuffer.
338         Notes: his is typically only called after you call `Finish()`.
339         Returns:
340             Returns the ByteBuffer for this FlatBuffer.
341     */
342     ByteBuffer dataBuffer()
343     {
344         return _buffer;
345     }
346 
347     /** 
348         A utility function to copy and return the ByteBuffer data as a `ubyte[]`
349         Retuens:
350             the byte used in FlatBuffer data, it is not copy.
351     */
352     ubyte[] sizedByteArray()
353     {
354         return _buffer.data[_buffer.position .. $];
355     }
356 
357     /**
358         Finalize a buffer, pointing to the given `rootTable`.
359         Params:
360             rootTable = An offset to be added to the buffer.
361             fileIdentifier = A FlatBuffer file identifier to be added to the buffer before `root_table`.
362     */
363     void finish(int rootTable, string fileIdentifier)
364     {
365         import std.string;
366 
367         prep(_minAlign, int.sizeof + fileIdentifierLength);
368         if (fileIdentifier.length != fileIdentifierLength)
369             throw new ArgumentException(
370                 format("FlatBuffers: file identifier must be length %s.", fileIdentifierLength),
371                 "fileIdentifier");
372         for (int i = fileIdentifierLength - 1; i >= 0; i--)
373             add!ubyte(cast(ubyte) fileIdentifier[i]);
374         addOffset(rootTable);
375     }
376 
377 private:
378     size_t _space;
379     ByteBuffer _buffer;
380     size_t _minAlign = 1;
381 
382     /// The vtable for the current table, null otherwise.
383 	size_t[] _vtable;
384     /// Starting offset of the current struct/table.
385     size_t _objectStart;
386     /// List of offsets of all vtables.
387 	size_t[] _vtables = new int[](16);
388     /// Number of entries in `vtables` in use.
389 	size_t _numVtables = 0;
390     /// For the current vector being built.
391 	size_t _vectorNumElems = 0;
392 }