• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 Google Inc. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from . import number_types as N
16from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags)
17
18from . import encode
19from . import packer
20
21from . import compat
22from .compat import range_func
23from .compat import memoryview_type
24
25
26## @file
27## @addtogroup flatbuffers_python_api
28## @{
29
30## @cond FLATBUFFERS_INTERNAL
31class OffsetArithmeticError(RuntimeError):
32    """
33    Error caused by an Offset arithmetic error. Probably caused by bad
34    writing of fields. This is considered an unreachable situation in
35    normal circumstances.
36    """
37    pass
38
39
40class IsNotNestedError(RuntimeError):
41    """
42    Error caused by using a Builder to write Object data when not inside
43    an Object.
44    """
45    pass
46
47
48class IsNestedError(RuntimeError):
49    """
50    Error caused by using a Builder to begin an Object when an Object is
51    already being built.
52    """
53    pass
54
55
56class StructIsNotInlineError(RuntimeError):
57    """
58    Error caused by using a Builder to write a Struct at a location that
59    is not the current Offset.
60    """
61    pass
62
63
64class BuilderSizeError(RuntimeError):
65    """
66    Error caused by causing a Builder to exceed the hardcoded limit of 2
67    gigabytes.
68    """
69    pass
70
71class BuilderNotFinishedError(RuntimeError):
72    """
73    Error caused by not calling `Finish` before calling `Output`.
74    """
75    pass
76
77
78# VtableMetadataFields is the count of metadata fields in each vtable.
79VtableMetadataFields = 2
80## @endcond
81
82class Builder(object):
83    """ A Builder is used to construct one or more FlatBuffers.
84
85    Typically, Builder objects will be used from code generated by the `flatc`
86    compiler.
87
88    A Builder constructs byte buffers in a last-first manner for simplicity and
89    performance during reading.
90
91    Internally, a Builder is a state machine for creating FlatBuffer objects.
92
93    It holds the following internal state:
94        - Bytes: an array of bytes.
95        - current_vtable: a list of integers.
96        - vtables: a list of vtable entries (i.e. a list of list of integers).
97
98    Attributes:
99      Bytes: The internal `bytearray` for the Builder.
100      finished: A boolean determining if the Builder has been finalized.
101    """
102
103    ## @cond FLATBUFFERS_INTENRAL
104    __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd",
105                 "vtables", "nested", "finished")
106
107    """Maximum buffer size constant, in bytes.
108
109    Builder will never allow it's buffer grow over this size.
110    Currently equals 2Gb.
111    """
112    MAX_BUFFER_SIZE = 2**31
113    ## @endcond
114
115    def __init__(self, initialSize):
116        """Initializes a Builder of size `initial_size`.
117
118        The internal buffer is grown as needed.
119        """
120
121        if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE):
122            msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes."
123            raise BuilderSizeError(msg)
124
125        self.Bytes = bytearray(initialSize)
126        ## @cond FLATBUFFERS_INTERNAL
127        self.current_vtable = None
128        self.head = UOffsetTFlags.py_type(initialSize)
129        self.minalign = 1
130        self.objectEnd = None
131        self.vtables = []
132        self.nested = False
133        ## @endcond
134        self.finished = False
135
136
137    def Output(self):
138        """Return the portion of the buffer that has been used for writing data.
139
140        This is the typical way to access the FlatBuffer data inside the
141        builder. If you try to access `Builder.Bytes` directly, you would need
142        to manually index it with `Head()`, since the buffer is constructed
143        backwards.
144
145        It raises BuilderNotFinishedError if the buffer has not been finished
146        with `Finish`.
147        """
148
149        if not self.finished:
150            raise BuilderNotFinishedError()
151
152        return self.Bytes[self.Head():]
153
154    ## @cond FLATBUFFERS_INTERNAL
155    def StartObject(self, numfields):
156        """StartObject initializes bookkeeping for writing a new object."""
157
158        self.assertNotNested()
159
160        # use 32-bit offsets so that arithmetic doesn't overflow.
161        self.current_vtable = [0 for _ in range_func(numfields)]
162        self.objectEnd = self.Offset()
163        self.minalign = 1
164        self.nested = True
165
166    def WriteVtable(self):
167        """
168        WriteVtable serializes the vtable for the current object, if needed.
169
170        Before writing out the vtable, this checks pre-existing vtables for
171        equality to this one. If an equal vtable is found, point the object to
172        the existing vtable and return.
173
174        Because vtable values are sensitive to alignment of object data, not
175        all logically-equal vtables will be deduplicated.
176
177        A vtable has the following format:
178          <VOffsetT: size of the vtable in bytes, including this value>
179          <VOffsetT: size of the object in bytes, including the vtable offset>
180          <VOffsetT: offset for a field> * N, where N is the number of fields
181                     in the schema for this type. Includes deprecated fields.
182        Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide.
183
184        An object has the following format:
185          <SOffsetT: offset to this object's vtable (may be negative)>
186          <byte: data>+
187        """
188
189        # Prepend a zero scalar to the object. Later in this function we'll
190        # write an offset here that points to the object's vtable:
191        self.PrependSOffsetTRelative(0)
192
193        objectOffset = self.Offset()
194        existingVtable = None
195
196        # Trim trailing 0 offsets.
197        while self.current_vtable and self.current_vtable[-1] == 0:
198            self.current_vtable.pop()
199
200        # Search backwards through existing vtables, because similar vtables
201        # are likely to have been recently appended. See
202        # BenchmarkVtableDeduplication for a case in which this heuristic
203        # saves about 30% of the time used in writing objects with duplicate
204        # tables.
205
206        i = len(self.vtables) - 1
207        while i >= 0:
208            # Find the other vtable, which is associated with `i`:
209            vt2Offset = self.vtables[i]
210            vt2Start = len(self.Bytes) - vt2Offset
211            vt2Len = encode.Get(packer.voffset, self.Bytes, vt2Start)
212
213            metadata = VtableMetadataFields * N.VOffsetTFlags.bytewidth
214            vt2End = vt2Start + vt2Len
215            vt2 = self.Bytes[vt2Start+metadata:vt2End]
216
217            # Compare the other vtable to the one under consideration.
218            # If they are equal, store the offset and break:
219            if vtableEqual(self.current_vtable, objectOffset, vt2):
220                existingVtable = vt2Offset
221                break
222
223            i -= 1
224
225        if existingVtable is None:
226            # Did not find a vtable, so write this one to the buffer.
227
228            # Write out the current vtable in reverse , because
229            # serialization occurs in last-first order:
230            i = len(self.current_vtable) - 1
231            while i >= 0:
232                off = 0
233                if self.current_vtable[i] != 0:
234                    # Forward reference to field;
235                    # use 32bit number to ensure no overflow:
236                    off = objectOffset - self.current_vtable[i]
237
238                self.PrependVOffsetT(off)
239                i -= 1
240
241            # The two metadata fields are written last.
242
243            # First, store the object bytesize:
244            objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd)
245            self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize))
246
247            # Second, store the vtable bytesize:
248            vBytes = len(self.current_vtable) + VtableMetadataFields
249            vBytes *= N.VOffsetTFlags.bytewidth
250            self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes))
251
252            # Next, write the offset to the new vtable in the
253            # already-allocated SOffsetT at the beginning of this object:
254            objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
255            encode.Write(packer.soffset, self.Bytes, objectStart,
256                         SOffsetTFlags.py_type(self.Offset() - objectOffset))
257
258            # Finally, store this vtable in memory for future
259            # deduplication:
260            self.vtables.append(self.Offset())
261        else:
262            # Found a duplicate vtable.
263
264            objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
265            self.head = UOffsetTFlags.py_type(objectStart)
266
267            # Write the offset to the found vtable in the
268            # already-allocated SOffsetT at the beginning of this object:
269            encode.Write(packer.soffset, self.Bytes, self.Head(),
270                         SOffsetTFlags.py_type(existingVtable - objectOffset))
271
272        self.current_vtable = None
273        return objectOffset
274
275    def EndObject(self):
276        """EndObject writes data necessary to finish object construction."""
277        self.assertNested()
278        self.nested = False
279        return self.WriteVtable()
280
281    def growByteBuffer(self):
282        """Doubles the size of the byteslice, and copies the old data towards
283           the end of the new buffer (since we build the buffer backwards)."""
284        if len(self.Bytes) == Builder.MAX_BUFFER_SIZE:
285            msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes"
286            raise BuilderSizeError(msg)
287
288        newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE)
289        if newSize == 0:
290            newSize = 1
291        bytes2 = bytearray(newSize)
292        bytes2[newSize-len(self.Bytes):] = self.Bytes
293        self.Bytes = bytes2
294    ## @endcond
295
296    def Head(self):
297        """Get the start of useful data in the underlying byte buffer.
298
299        Note: unlike other functions, this value is interpreted as from the
300        left.
301        """
302        ## @cond FLATBUFFERS_INTERNAL
303        return self.head
304        ## @endcond
305
306    ## @cond FLATBUFFERS_INTERNAL
307    def Offset(self):
308        """Offset relative to the end of the buffer."""
309        return UOffsetTFlags.py_type(len(self.Bytes) - self.Head())
310
311    def Pad(self, n):
312        """Pad places zeros at the current offset."""
313        for i in range_func(n):
314            self.Place(0, N.Uint8Flags)
315
316    def Prep(self, size, additionalBytes):
317        """
318        Prep prepares to write an element of `size` after `additional_bytes`
319        have been written, e.g. if you write a string, you need to align
320        such the int length field is aligned to SizeInt32, and the string
321        data follows it directly.
322        If all you need to do is align, `additionalBytes` will be 0.
323        """
324
325        # Track the biggest thing we've ever aligned to.
326        if size > self.minalign:
327            self.minalign = size
328
329        # Find the amount of alignment needed such that `size` is properly
330        # aligned after `additionalBytes`:
331        alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1
332        alignSize &= (size - 1)
333
334        # Reallocate the buffer if needed:
335        while self.Head() < alignSize+size+additionalBytes:
336            oldBufSize = len(self.Bytes)
337            self.growByteBuffer()
338            updated_head = self.head + len(self.Bytes) - oldBufSize
339            self.head = UOffsetTFlags.py_type(updated_head)
340        self.Pad(alignSize)
341
342    def PrependSOffsetTRelative(self, off):
343        """
344        PrependSOffsetTRelative prepends an SOffsetT, relative to where it
345        will be written.
346        """
347
348        # Ensure alignment is already done:
349        self.Prep(N.SOffsetTFlags.bytewidth, 0)
350        if not (off <= self.Offset()):
351            msg = "flatbuffers: Offset arithmetic error."
352            raise OffsetArithmeticError(msg)
353        off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth
354        self.PlaceSOffsetT(off2)
355    ## @endcond
356
357    def PrependUOffsetTRelative(self, off):
358        """Prepends an unsigned offset into vector data, relative to where it
359        will be written.
360        """
361
362        # Ensure alignment is already done:
363        self.Prep(N.UOffsetTFlags.bytewidth, 0)
364        if not (off <= self.Offset()):
365            msg = "flatbuffers: Offset arithmetic error."
366            raise OffsetArithmeticError(msg)
367        off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth
368        self.PlaceUOffsetT(off2)
369
370    ## @cond FLATBUFFERS_INTERNAL
371    def StartVector(self, elemSize, numElems, alignment):
372        """
373        StartVector initializes bookkeeping for writing a new vector.
374
375        A vector has the following format:
376          - <UOffsetT: number of elements in this vector>
377          - <T: data>+, where T is the type of elements of this vector.
378        """
379
380        self.assertNotNested()
381        self.nested = True
382        self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems)
383        self.Prep(alignment, elemSize*numElems)  # In case alignment > int.
384        return self.Offset()
385    ## @endcond
386
387    def EndVector(self, vectorNumElems):
388        """EndVector writes data necessary to finish vector construction."""
389
390        self.assertNested()
391        ## @cond FLATBUFFERS_INTERNAL
392        self.nested = False
393        ## @endcond
394        # we already made space for this, so write without PrependUint32
395        self.PlaceUOffsetT(vectorNumElems)
396        return self.Offset()
397
398    def CreateString(self, s, encoding='utf-8', errors='strict'):
399        """CreateString writes a null-terminated byte string as a vector."""
400
401        self.assertNotNested()
402        ## @cond FLATBUFFERS_INTERNAL
403        self.nested = True
404        ## @endcond
405
406        if isinstance(s, compat.string_types):
407            x = s.encode(encoding, errors)
408        elif isinstance(s, compat.binary_types):
409            x = s
410        else:
411            raise TypeError("non-string passed to CreateString")
412
413        self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth)
414        self.Place(0, N.Uint8Flags)
415
416        l = UOffsetTFlags.py_type(len(s))
417        ## @cond FLATBUFFERS_INTERNAL
418        self.head = UOffsetTFlags.py_type(self.Head() - l)
419        ## @endcond
420        self.Bytes[self.Head():self.Head()+l] = x
421
422        return self.EndVector(len(x))
423
424    def CreateByteVector(self, x):
425        """CreateString writes a byte vector."""
426
427        self.assertNotNested()
428        ## @cond FLATBUFFERS_INTERNAL
429        self.nested = True
430        ## @endcond
431
432        if not isinstance(x, compat.binary_types):
433            raise TypeError("non-byte vector passed to CreateByteVector")
434
435        self.Prep(N.UOffsetTFlags.bytewidth, len(x)*N.Uint8Flags.bytewidth)
436
437        l = UOffsetTFlags.py_type(len(x))
438        ## @cond FLATBUFFERS_INTERNAL
439        self.head = UOffsetTFlags.py_type(self.Head() - l)
440        ## @endcond
441        self.Bytes[self.Head():self.Head()+l] = x
442
443        return self.EndVector(len(x))
444
445    ## @cond FLATBUFFERS_INTERNAL
446    def assertNested(self):
447        """
448        Check that we are in the process of building an object.
449        """
450
451        if not self.nested:
452            raise IsNotNestedError()
453
454    def assertNotNested(self):
455        """
456        Check that no other objects are being built while making this
457        object. If not, raise an exception.
458        """
459
460        if self.nested:
461            raise IsNestedError()
462
463    def assertStructIsInline(self, obj):
464        """
465        Structs are always stored inline, so need to be created right
466        where they are used. You'll get this error if you created it
467        elsewhere.
468        """
469
470        N.enforce_number(obj, N.UOffsetTFlags)
471        if obj != self.Offset():
472            msg = ("flatbuffers: Tried to write a Struct at an Offset that "
473                   "is different from the current Offset of the Builder.")
474            raise StructIsNotInlineError(msg)
475
476    def Slot(self, slotnum):
477        """
478        Slot sets the vtable key `voffset` to the current location in the
479        buffer.
480
481        """
482        self.assertNested()
483        self.current_vtable[slotnum] = self.Offset()
484    ## @endcond
485
486    def Finish(self, rootTable):
487        """Finish finalizes a buffer, pointing to the given `rootTable`."""
488        N.enforce_number(rootTable, N.UOffsetTFlags)
489        self.Prep(self.minalign, N.UOffsetTFlags.bytewidth)
490        self.PrependUOffsetTRelative(rootTable)
491        self.finished = True
492        return self.Head()
493
494    ## @cond FLATBUFFERS_INTERNAL
495    def Prepend(self, flags, off):
496        self.Prep(flags.bytewidth, 0)
497        self.Place(off, flags)
498
499    def PrependSlot(self, flags, o, x, d):
500        N.enforce_number(x, flags)
501        N.enforce_number(d, flags)
502        if x != d:
503            self.Prepend(flags, x)
504            self.Slot(o)
505
506    def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args)
507
508    def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
509
510    def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
511
512    def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args)
513
514    def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args)
515
516    def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args)
517
518    def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args)
519
520    def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args)
521
522    def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args)
523
524    def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args)
525
526    def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags,
527                                                          *args)
528
529    def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags,
530                                                          *args)
531
532    def PrependUOffsetTRelativeSlot(self, o, x, d):
533        """
534        PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at
535        vtable slot `o`. If value `x` equals default `d`, then the slot will
536        be set to zero and no other data will be written.
537        """
538
539        if x != d:
540            self.PrependUOffsetTRelative(x)
541            self.Slot(o)
542
543    def PrependStructSlot(self, v, x, d):
544        """
545        PrependStructSlot prepends a struct onto the object at vtable slot `o`.
546        Structs are stored inline, so nothing additional is being added.
547        In generated code, `d` is always 0.
548        """
549
550        N.enforce_number(d, N.UOffsetTFlags)
551        if x != d:
552            self.assertStructIsInline(x)
553            self.Slot(v)
554
555    ## @endcond
556
557    def PrependBool(self, x):
558        """Prepend a `bool` to the Builder buffer.
559
560        Note: aligns and checks for space.
561        """
562        self.Prepend(N.BoolFlags, x)
563
564    def PrependByte(self, x):
565        """Prepend a `byte` to the Builder buffer.
566
567        Note: aligns and checks for space.
568        """
569        self.Prepend(N.Uint8Flags, x)
570
571    def PrependUint8(self, x):
572        """Prepend an `uint8` to the Builder buffer.
573
574        Note: aligns and checks for space.
575        """
576        self.Prepend(N.Uint8Flags, x)
577
578    def PrependUint16(self, x):
579        """Prepend an `uint16` to the Builder buffer.
580
581        Note: aligns and checks for space.
582        """
583        self.Prepend(N.Uint16Flags, x)
584
585    def PrependUint32(self, x):
586        """Prepend an `uint32` to the Builder buffer.
587
588        Note: aligns and checks for space.
589        """
590        self.Prepend(N.Uint32Flags, x)
591
592    def PrependUint64(self, x):
593        """Prepend an `uint64` to the Builder buffer.
594
595        Note: aligns and checks for space.
596        """
597        self.Prepend(N.Uint64Flags, x)
598
599    def PrependInt8(self, x):
600        """Prepend an `int8` to the Builder buffer.
601
602        Note: aligns and checks for space.
603        """
604        self.Prepend(N.Int8Flags, x)
605
606    def PrependInt16(self, x):
607        """Prepend an `int16` to the Builder buffer.
608
609        Note: aligns and checks for space.
610        """
611        self.Prepend(N.Int16Flags, x)
612
613    def PrependInt32(self, x):
614        """Prepend an `int32` to the Builder buffer.
615
616        Note: aligns and checks for space.
617        """
618        self.Prepend(N.Int32Flags, x)
619
620    def PrependInt64(self, x):
621        """Prepend an `int64` to the Builder buffer.
622
623        Note: aligns and checks for space.
624        """
625        self.Prepend(N.Int64Flags, x)
626
627    def PrependFloat32(self, x):
628        """Prepend a `float32` to the Builder buffer.
629
630        Note: aligns and checks for space.
631        """
632        self.Prepend(N.Float32Flags, x)
633
634    def PrependFloat64(self, x):
635        """Prepend a `float64` to the Builder buffer.
636
637        Note: aligns and checks for space.
638        """
639        self.Prepend(N.Float64Flags, x)
640
641##############################################################
642
643    ## @cond FLATBUFFERS_INTERNAL
644    def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x)
645
646    def Place(self, x, flags):
647        """
648        Place prepends a value specified by `flags` to the Builder,
649        without checking for available space.
650        """
651
652        N.enforce_number(x, flags)
653        self.head = self.head - flags.bytewidth
654        encode.Write(flags.packer_type, self.Bytes, self.Head(), x)
655
656    def PlaceVOffsetT(self, x):
657        """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking
658        for space.
659        """
660        N.enforce_number(x, N.VOffsetTFlags)
661        self.head = self.head - N.VOffsetTFlags.bytewidth
662        encode.Write(packer.voffset, self.Bytes, self.Head(), x)
663
664    def PlaceSOffsetT(self, x):
665        """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking
666        for space.
667        """
668        N.enforce_number(x, N.SOffsetTFlags)
669        self.head = self.head - N.SOffsetTFlags.bytewidth
670        encode.Write(packer.soffset, self.Bytes, self.Head(), x)
671
672    def PlaceUOffsetT(self, x):
673        """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking
674        for space.
675        """
676        N.enforce_number(x, N.UOffsetTFlags)
677        self.head = self.head - N.UOffsetTFlags.bytewidth
678        encode.Write(packer.uoffset, self.Bytes, self.Head(), x)
679    ## @endcond
680
681## @cond FLATBUFFERS_INTERNAL
682def vtableEqual(a, objectStart, b):
683    """vtableEqual compares an unwritten vtable to a written vtable."""
684
685    N.enforce_number(objectStart, N.UOffsetTFlags)
686
687    if len(a) * N.VOffsetTFlags.bytewidth != len(b):
688        return False
689
690    for i, elem in enumerate(a):
691        x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth)
692
693        # Skip vtable entries that indicate a default value.
694        if x == 0 and elem == 0:
695            pass
696        else:
697            y = objectStart - elem
698            if x != y:
699                return False
700    return True
701## @endcond
702## @}
703