1/*
2 * Copyright 2014 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
18using System;
19using System.Text;
20
21/// @file
22/// @addtogroup flatbuffers_csharp_api
23/// @{
24
25namespace FlatBuffers
26{
27    /// <summary>
28    /// Responsible for building up and accessing a FlatBuffer formatted byte
29    /// array (via ByteBuffer).
30    /// </summary>
31    public class FlatBufferBuilder
32    {
33        private int _space;
34        private ByteBuffer _bb;
35        private int _minAlign = 1;
36
37        // The vtable for the current table (if _vtableSize >= 0)
38        private int[] _vtable = new int[16];
39        // The size of the vtable. -1 indicates no vtable
40        private int _vtableSize = -1;
41        // Starting offset of the current struct/table.
42        private int _objectStart;
43        // List of offsets of all vtables.
44        private int[] _vtables = new int[16];
45        // Number of entries in `vtables` in use.
46        private int _numVtables = 0;
47        // For the current vector being built.
48        private int _vectorNumElems = 0;
49
50        /// <summary>
51        /// Create a FlatBufferBuilder with a given initial size.
52        /// </summary>
53        /// <param name="initialSize">
54        /// The initial size to use for the internal buffer.
55        /// </param>
56        public FlatBufferBuilder(int initialSize)
57        {
58            if (initialSize <= 0)
59                throw new ArgumentOutOfRangeException("initialSize",
60                    initialSize, "Must be greater than zero");
61            _space = initialSize;
62            _bb = new ByteBuffer(new byte[initialSize]);
63        }
64
65        /// <summary>
66        /// Reset the FlatBufferBuilder by purging all data that it holds.
67        /// </summary>
68        public void Clear()
69        {
70            _space = _bb.Length;
71            _bb.Reset();
72            _minAlign = 1;
73            while (_vtableSize > 0) _vtable[--_vtableSize] = 0;
74            _vtableSize = -1;
75            _objectStart = 0;
76            _numVtables = 0;
77            _vectorNumElems = 0;
78        }
79
80        /// <summary>
81        /// Gets and sets a Boolean to disable the optimization when serializing
82        /// default values to a Table.
83        ///
84        /// In order to save space, fields that are set to their default value
85        /// don't get serialized into the buffer.
86        /// </summary>
87        public bool ForceDefaults { get; set; }
88
89        /// @cond FLATBUFFERS_INTERNAL
90
91        public int Offset { get { return _bb.Length - _space; } }
92
93        public void Pad(int size)
94        {
95             _bb.PutByte(_space -= size, 0, size);
96        }
97
98        // Doubles the size of the ByteBuffer, and copies the old data towards
99        // the end of the new buffer (since we build the buffer backwards).
100        void GrowBuffer()
101        {
102            var oldBuf = _bb.Data;
103            var oldBufSize = oldBuf.Length;
104            if ((oldBufSize & 0xC0000000) != 0)
105                throw new Exception(
106                    "FlatBuffers: cannot grow buffer beyond 2 gigabytes.");
107
108            var newBufSize = oldBufSize << 1;
109            var newBuf = new byte[newBufSize];
110
111            Buffer.BlockCopy(oldBuf, 0, newBuf, newBufSize - oldBufSize,
112                             oldBufSize);
113            _bb = new ByteBuffer(newBuf, newBufSize);
114        }
115
116        // Prepare to write an element of `size` after `additional_bytes`
117        // have been written, e.g. if you write a string, you need to align
118        // such the int length field is aligned to SIZEOF_INT, and the string
119        // data follows it directly.
120        // If all you need to do is align, `additional_bytes` will be 0.
121        public void Prep(int size, int additionalBytes)
122        {
123            // Track the biggest thing we've ever aligned to.
124            if (size > _minAlign)
125                _minAlign = size;
126            // Find the amount of alignment needed such that `size` is properly
127            // aligned after `additional_bytes`
128            var alignSize =
129                ((~((int)_bb.Length - _space + additionalBytes)) + 1) &
130                (size - 1);
131            // Reallocate the buffer if needed.
132            while (_space < alignSize + size + additionalBytes)
133            {
134                var oldBufSize = (int)_bb.Length;
135                GrowBuffer();
136                _space += (int)_bb.Length - oldBufSize;
137
138            }
139            if (alignSize > 0)
140                Pad(alignSize);
141        }
142
143        public void PutBool(bool x)
144        {
145          _bb.PutByte(_space -= sizeof(byte), (byte)(x ? 1 : 0));
146        }
147
148        public void PutSbyte(sbyte x)
149        {
150          _bb.PutSbyte(_space -= sizeof(sbyte), x);
151        }
152
153        public void PutByte(byte x)
154        {
155            _bb.PutByte(_space -= sizeof(byte), x);
156        }
157
158        public void PutShort(short x)
159        {
160            _bb.PutShort(_space -= sizeof(short), x);
161        }
162
163        public void PutUshort(ushort x)
164        {
165          _bb.PutUshort(_space -= sizeof(ushort), x);
166        }
167
168        public void PutInt(int x)
169        {
170            _bb.PutInt(_space -= sizeof(int), x);
171        }
172
173        public void PutUint(uint x)
174        {
175          _bb.PutUint(_space -= sizeof(uint), x);
176        }
177
178        public void PutLong(long x)
179        {
180            _bb.PutLong(_space -= sizeof(long), x);
181        }
182
183        public void PutUlong(ulong x)
184        {
185          _bb.PutUlong(_space -= sizeof(ulong), x);
186        }
187
188        public void PutFloat(float x)
189        {
190            _bb.PutFloat(_space -= sizeof(float), x);
191        }
192
193        public void PutDouble(double x)
194        {
195            _bb.PutDouble(_space -= sizeof(double), x);
196        }
197        /// @endcond
198
199        /// <summary>
200        /// Add a `bool` to the buffer (aligns the data and grows if necessary).
201        /// </summary>
202        /// <param name="x">The `bool` to add to the buffer.</param>
203        public void AddBool(bool x) { Prep(sizeof(byte), 0); PutBool(x); }
204
205        /// <summary>
206        /// Add a `sbyte` to the buffer (aligns the data and grows if necessary).
207        /// </summary>
208        /// <param name="x">The `sbyte` to add to the buffer.</param>
209        public void AddSbyte(sbyte x) { Prep(sizeof(sbyte), 0); PutSbyte(x); }
210
211        /// <summary>
212        /// Add a `byte` to the buffer (aligns the data and grows if necessary).
213        /// </summary>
214        /// <param name="x">The `byte` to add to the buffer.</param>
215        public void AddByte(byte x) { Prep(sizeof(byte), 0); PutByte(x); }
216
217        /// <summary>
218        /// Add a `short` to the buffer (aligns the data and grows if necessary).
219        /// </summary>
220        /// <param name="x">The `short` to add to the buffer.</param>
221        public void AddShort(short x) { Prep(sizeof(short), 0); PutShort(x); }
222
223        /// <summary>
224        /// Add an `ushort` to the buffer (aligns the data and grows if necessary).
225        /// </summary>
226        /// <param name="x">The `ushort` to add to the buffer.</param>
227        public void AddUshort(ushort x) { Prep(sizeof(ushort), 0); PutUshort(x); }
228
229        /// <summary>
230        /// Add an `int` to the buffer (aligns the data and grows if necessary).
231        /// </summary>
232        /// <param name="x">The `int` to add to the buffer.</param>
233        public void AddInt(int x) { Prep(sizeof(int), 0); PutInt(x); }
234
235        /// <summary>
236        /// Add an `uint` to the buffer (aligns the data and grows if necessary).
237        /// </summary>
238        /// <param name="x">The `uint` to add to the buffer.</param>
239        public void AddUint(uint x) { Prep(sizeof(uint), 0); PutUint(x); }
240
241        /// <summary>
242        /// Add a `long` to the buffer (aligns the data and grows if necessary).
243        /// </summary>
244        /// <param name="x">The `long` to add to the buffer.</param>
245        public void AddLong(long x) { Prep(sizeof(long), 0); PutLong(x); }
246
247        /// <summary>
248        /// Add an `ulong` to the buffer (aligns the data and grows if necessary).
249        /// </summary>
250        /// <param name="x">The `ulong` to add to the buffer.</param>
251        public void AddUlong(ulong x) { Prep(sizeof(ulong), 0); PutUlong(x); }
252
253        /// <summary>
254        /// Add a `float` to the buffer (aligns the data and grows if necessary).
255        /// </summary>
256        /// <param name="x">The `float` to add to the buffer.</param>
257        public void AddFloat(float x) { Prep(sizeof(float), 0); PutFloat(x); }
258
259        /// <summary>
260        /// Add a `double` to the buffer (aligns the data and grows if necessary).
261        /// </summary>
262        /// <param name="x">The `double` to add to the buffer.</param>
263        public void AddDouble(double x) { Prep(sizeof(double), 0);
264                                          PutDouble(x); }
265
266        /// <summary>
267        /// Adds an offset, relative to where it will be written.
268        /// </summary>
269        /// <param name="off">The offset to add to the buffer.</param>
270        public void AddOffset(int off)
271        {
272            Prep(sizeof(int), 0);  // Ensure alignment is already done.
273            if (off > Offset)
274                throw new ArgumentException();
275
276            off = Offset - off + sizeof(int);
277            PutInt(off);
278        }
279
280        /// @cond FLATBUFFERS_INTERNAL
281        public void StartVector(int elemSize, int count, int alignment)
282        {
283            NotNested();
284            _vectorNumElems = count;
285            Prep(sizeof(int), elemSize * count);
286            Prep(alignment, elemSize * count); // Just in case alignment > int.
287        }
288        /// @endcond
289
290        /// <summary>
291        /// Writes data necessary to finish a vector construction.
292        /// </summary>
293        public VectorOffset EndVector()
294        {
295            PutInt(_vectorNumElems);
296            return new VectorOffset(Offset);
297        }
298
299        /// <summary>
300        /// Creates a vector of tables.
301        /// </summary>
302        /// <param name="offsets">Offsets of the tables.</param>
303        public VectorOffset CreateVectorOfTables<T>(Offset<T>[] offsets) where T : struct
304        {
305            NotNested();
306            StartVector(sizeof(int), offsets.Length, sizeof(int));
307            for (int i = offsets.Length - 1; i >= 0; i--) AddOffset(offsets[i].Value);
308            return EndVector();
309        }
310
311        /// @cond FLATBUFFERS_INTENRAL
312        public void Nested(int obj)
313        {
314            // Structs are always stored inline, so need to be created right
315            // where they are used. You'll get this assert if you created it
316            // elsewhere.
317            if (obj != Offset)
318                throw new Exception(
319                    "FlatBuffers: struct must be serialized inline.");
320        }
321
322        public void NotNested()
323        {
324            // You should not be creating any other objects or strings/vectors
325            // while an object is being constructed
326            if (_vtableSize >= 0)
327                throw new Exception(
328                    "FlatBuffers: object serialization must not be nested.");
329        }
330
331        public void StartObject(int numfields)
332        {
333            if (numfields < 0)
334                throw new ArgumentOutOfRangeException("Flatbuffers: invalid numfields");
335
336            NotNested();
337
338            if (_vtable.Length < numfields)
339                _vtable = new int[numfields];
340
341            _vtableSize = numfields;
342            _objectStart = Offset;
343        }
344
345
346        // Set the current vtable at `voffset` to the current location in the
347        // buffer.
348        public void Slot(int voffset)
349        {
350            if (voffset >= _vtableSize)
351                throw new IndexOutOfRangeException("Flatbuffers: invalid voffset");
352
353            _vtable[voffset] = Offset;
354        }
355
356        /// <summary>
357        /// Adds a Boolean to the Table at index `o` in its vtable using the value `x` and default `d`
358        /// </summary>
359        /// <param name="o">The index into the vtable</param>
360        /// <param name="x">The value to put into the buffer. If the value is equal to the default
361        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
362        /// <param name="d">The default value to compare the value against</param>
363        public void AddBool(int o, bool x, bool d) { if (ForceDefaults || x != d) { AddBool(x); Slot(o); } }
364
365        /// <summary>
366        /// Adds a SByte to the Table at index `o` in its vtable using the value `x` and default `d`
367        /// </summary>
368        /// <param name="o">The index into the vtable</param>
369        /// <param name="x">The value to put into the buffer. If the value is equal to the default
370        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
371        /// <param name="d">The default value to compare the value against</param>
372        public void AddSbyte(int o, sbyte x, sbyte d) { if (ForceDefaults || x != d) { AddSbyte(x); Slot(o); } }
373
374        /// <summary>
375        /// Adds a Byte to the Table at index `o` in its vtable using the value `x` and default `d`
376        /// </summary>
377        /// <param name="o">The index into the vtable</param>
378        /// <param name="x">The value to put into the buffer. If the value is equal to the default
379        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
380        /// <param name="d">The default value to compare the value against</param>
381        public void AddByte(int o, byte x, byte d) { if (ForceDefaults || x != d) { AddByte(x); Slot(o); } }
382
383        /// <summary>
384        /// Adds a Int16 to the Table at index `o` in its vtable using the value `x` and default `d`
385        /// </summary>
386        /// <param name="o">The index into the vtable</param>
387        /// <param name="x">The value to put into the buffer. If the value is equal to the default
388        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
389        /// <param name="d">The default value to compare the value against</param>
390        public void AddShort(int o, short x, int d) { if (ForceDefaults || x != d) { AddShort(x); Slot(o); } }
391
392        /// <summary>
393        /// Adds a UInt16 to the Table at index `o` in its vtable using the value `x` and default `d`
394        /// </summary>
395        /// <param name="o">The index into the vtable</param>
396        /// <param name="x">The value to put into the buffer. If the value is equal to the default
397        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
398        /// <param name="d">The default value to compare the value against</param>
399        public void AddUshort(int o, ushort x, ushort d) { if (ForceDefaults || x != d) { AddUshort(x); Slot(o); } }
400
401        /// <summary>
402        /// Adds an Int32 to the Table at index `o` in its vtable using the value `x` and default `d`
403        /// </summary>
404        /// <param name="o">The index into the vtable</param>
405        /// <param name="x">The value to put into the buffer. If the value is equal to the default
406        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
407        /// <param name="d">The default value to compare the value against</param>
408        public void AddInt(int o, int x, int d) { if (ForceDefaults || x != d) { AddInt(x); Slot(o); } }
409
410        /// <summary>
411        /// Adds a UInt32 to the Table at index `o` in its vtable using the value `x` and default `d`
412        /// </summary>
413        /// <param name="o">The index into the vtable</param>
414        /// <param name="x">The value to put into the buffer. If the value is equal to the default
415        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
416        /// <param name="d">The default value to compare the value against</param>
417        public void AddUint(int o, uint x, uint d) { if (ForceDefaults || x != d) { AddUint(x); Slot(o); } }
418
419        /// <summary>
420        /// Adds an Int64 to the Table at index `o` in its vtable using the value `x` and default `d`
421        /// </summary>
422        /// <param name="o">The index into the vtable</param>
423        /// <param name="x">The value to put into the buffer. If the value is equal to the default
424        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
425        /// <param name="d">The default value to compare the value against</param>
426        public void AddLong(int o, long x, long d) { if (ForceDefaults || x != d) { AddLong(x); Slot(o); } }
427
428        /// <summary>
429        /// Adds a UInt64 to the Table at index `o` in its vtable using the value `x` and default `d`
430        /// </summary>
431        /// <param name="o">The index into the vtable</param>
432        /// <param name="x">The value to put into the buffer. If the value is equal to the default
433        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
434        /// <param name="d">The default value to compare the value against</param>
435        public void AddUlong(int o, ulong x, ulong d) { if (ForceDefaults || x != d) { AddUlong(x); Slot(o); } }
436
437        /// <summary>
438        /// Adds a Single to the Table at index `o` in its vtable using the value `x` and default `d`
439        /// </summary>
440        /// <param name="o">The index into the vtable</param>
441        /// <param name="x">The value to put into the buffer. If the value is equal to the default
442        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
443        /// <param name="d">The default value to compare the value against</param>
444        public void AddFloat(int o, float x, double d) { if (ForceDefaults || x != d) { AddFloat(x); Slot(o); } }
445
446        /// <summary>
447        /// Adds a Double to the Table at index `o` in its vtable using the value `x` and default `d`
448        /// </summary>
449        /// <param name="o">The index into the vtable</param>
450        /// <param name="x">The value to put into the buffer. If the value is equal to the default
451        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
452        /// <param name="d">The default value to compare the value against</param>
453        public void AddDouble(int o, double x, double d) { if (ForceDefaults || x != d) { AddDouble(x); Slot(o); } }
454
455        /// <summary>
456        /// Adds a buffer offset to the Table at index `o` in its vtable using the value `x` and default `d`
457        /// </summary>
458        /// <param name="o">The index into the vtable</param>
459        /// <param name="x">The value to put into the buffer. If the value is equal to the default
460        /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param>
461        /// <param name="d">The default value to compare the value against</param>
462        public void AddOffset(int o, int x, int d) { if (ForceDefaults || x != d) { AddOffset(x); Slot(o); } }
463        /// @endcond
464
465        /// <summary>
466        /// Encode the string `s` in the buffer using UTF-8.
467        /// </summary>
468        /// <param name="s">The string to encode.</param>
469        /// <returns>
470        /// The offset in the buffer where the encoded string starts.
471        /// </returns>
472        public StringOffset CreateString(string s)
473        {
474            NotNested();
475            AddByte(0);
476            var utf8StringLen = Encoding.UTF8.GetByteCount(s);
477            StartVector(1, utf8StringLen, 1);
478            Encoding.UTF8.GetBytes(s, 0, s.Length, _bb.Data, _space -= utf8StringLen);
479            return new StringOffset(EndVector().Value);
480        }
481
482        /// @cond FLATBUFFERS_INTERNAL
483        // Structs are stored inline, so nothing additional is being added.
484        // `d` is always 0.
485        public void AddStruct(int voffset, int x, int d)
486        {
487            if (x != d)
488            {
489                Nested(x);
490                Slot(voffset);
491            }
492        }
493
494        public int EndObject()
495        {
496            if (_vtableSize < 0)
497                throw new InvalidOperationException(
498                  "Flatbuffers: calling endObject without a startObject");
499
500            AddInt((int)0);
501            var vtableloc = Offset;
502            // Write out the current vtable.
503            for (int i = _vtableSize - 1; i >= 0 ; i--) {
504                // Offset relative to the start of the table.
505                short off = (short)(_vtable[i] != 0
506                                        ? vtableloc - _vtable[i]
507                                        : 0);
508                AddShort(off);
509
510                // clear out written entry
511                _vtable[i] = 0;
512            }
513
514            const int standardFields = 2; // The fields below:
515            AddShort((short)(vtableloc - _objectStart));
516            AddShort((short)((_vtableSize + standardFields) *
517                             sizeof(short)));
518
519            // Search for an existing vtable that matches the current one.
520            int existingVtable = 0;
521            for (int i = 0; i < _numVtables; i++) {
522                int vt1 = _bb.Length - _vtables[i];
523                int vt2 = _space;
524                short len = _bb.GetShort(vt1);
525                if (len == _bb.GetShort(vt2)) {
526                    for (int j = sizeof(short); j < len; j += sizeof(short)) {
527                        if (_bb.GetShort(vt1 + j) != _bb.GetShort(vt2 + j)) {
528                            goto endLoop;
529                        }
530                    }
531                    existingVtable = _vtables[i];
532                    break;
533                }
534
535            endLoop: { }
536            }
537
538            if (existingVtable != 0) {
539                // Found a match:
540                // Remove the current vtable.
541                _space = _bb.Length - vtableloc;
542                // Point table to existing vtable.
543                _bb.PutInt(_space, existingVtable - vtableloc);
544            } else {
545                // No match:
546                // Add the location of the current vtable to the list of
547                // vtables.
548                if (_numVtables == _vtables.Length)
549                {
550                    // Arrays.CopyOf(vtables num_vtables * 2);
551                    var newvtables = new int[ _numVtables * 2];
552                    Array.Copy(_vtables, newvtables, _vtables.Length);
553
554                    _vtables = newvtables;
555                };
556                _vtables[_numVtables++] = Offset;
557                // Point table to current vtable.
558                _bb.PutInt(_bb.Length - vtableloc, Offset - vtableloc);
559            }
560
561            _vtableSize = -1;
562            return vtableloc;
563        }
564
565        // This checks a required field has been set in a given table that has
566        // just been constructed.
567        public void Required(int table, int field)
568        {
569          int table_start = _bb.Length - table;
570          int vtable_start = table_start - _bb.GetInt(table_start);
571          bool ok = _bb.GetShort(vtable_start + field) != 0;
572          // If this fails, the caller will show what field needs to be set.
573          if (!ok)
574            throw new InvalidOperationException("FlatBuffers: field " + field +
575                                                " must be set");
576        }
577        /// @endcond
578
579        /// <summary>
580        /// Finalize a buffer, pointing to the given `root_table`.
581        /// </summary>
582        /// <param name="rootTable">
583        /// An offset to be added to the buffer.
584        /// </param>
585        public void Finish(int rootTable)
586        {
587            Prep(_minAlign, sizeof(int));
588            AddOffset(rootTable);
589            _bb.Position = _space;
590        }
591
592        /// <summary>
593        /// Get the ByteBuffer representing the FlatBuffer.
594        /// </summary>
595        /// <remarks>
596        /// This is typically only called after you call `Finish()`.
597        /// The actual data starts at the ByteBuffer's current position,
598        /// not necessarily at `0`.
599        /// </remarks>
600        /// <returns>
601        /// Returns the ByteBuffer for this FlatBuffer.
602        /// </returns>
603        public ByteBuffer DataBuffer { get { return _bb; } }
604
605        /// <summary>
606        /// A utility function to copy and return the ByteBuffer data as a
607        /// `byte[]`.
608        /// </summary>
609        /// <returns>
610        /// A full copy of the FlatBuffer data.
611        /// </returns>
612        public byte[] SizedByteArray()
613        {
614            var newArray = new byte[_bb.Data.Length - _bb.Position];
615            Buffer.BlockCopy(_bb.Data, _bb.Position, newArray, 0,
616                             _bb.Data.Length - _bb.Position);
617            return newArray;
618        }
619
620         /// <summary>
621         /// Finalize a buffer, pointing to the given `rootTable`.
622         /// </summary>
623         /// <param name="rootTable">
624         /// An offset to be added to the buffer.
625         /// </param>
626         /// <param name="fileIdentifier">
627         /// A FlatBuffer file identifier to be added to the buffer before
628         /// `root_table`.
629         /// </param>
630         public void Finish(int rootTable, string fileIdentifier)
631         {
632             Prep(_minAlign, sizeof(int) +
633                             FlatBufferConstants.FileIdentifierLength);
634             if (fileIdentifier.Length !=
635                 FlatBufferConstants.FileIdentifierLength)
636                 throw new ArgumentException(
637                     "FlatBuffers: file identifier must be length " +
638                     FlatBufferConstants.FileIdentifierLength,
639                     "fileIdentifier");
640             for (int i = FlatBufferConstants.FileIdentifierLength - 1; i >= 0;
641                  i--)
642             {
643                AddByte((byte)fileIdentifier[i]);
644             }
645             Finish(rootTable);
646        }
647
648
649    }
650}
651
652/// @}
653