| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library analyzer.src.summary.flat_buffers; |
| |
| import 'dart:collection'; |
| import 'dart:convert'; |
| import 'dart:math'; |
| import 'dart:typed_data'; |
| |
| /** |
| * Reader of lists of boolean values. |
| * |
| * The returned unmodifiable lists lazily read values on access. |
| */ |
| class BoolListReader extends Reader<List<bool>> { |
| const BoolListReader(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| List<bool> read(BufferContext bc, int offset) => |
| new _FbBoolList(bc, bc.derefObject(offset)); |
| } |
| |
| /** |
| * The reader of booleans. |
| */ |
| class BoolReader extends Reader<bool> { |
| const BoolReader() : super(); |
| |
| @override |
| int get size => 1; |
| |
| @override |
| bool read(BufferContext bc, int offset) => bc._getInt8(offset) != 0; |
| } |
| |
| /** |
| * Buffer with data and some context about it. |
| */ |
| class BufferContext { |
| final ByteData _buffer; |
| |
| factory BufferContext.fromBytes(List<int> byteList) { |
| Uint8List uint8List = _asUint8List(byteList); |
| ByteData buf = new ByteData.view(uint8List.buffer, uint8List.offsetInBytes); |
| return new BufferContext._(buf); |
| } |
| |
| BufferContext._(this._buffer); |
| |
| int derefObject(int offset) { |
| return offset + _getUint32(offset); |
| } |
| |
| Uint8List _asUint8LIst(int offset, int length) => |
| _buffer.buffer.asUint8List(_buffer.offsetInBytes + offset, length); |
| |
| double _getFloat64(int offset) => |
| _buffer.getFloat64(offset, Endianness.LITTLE_ENDIAN); |
| |
| int _getInt32(int offset) => |
| _buffer.getInt32(offset, Endianness.LITTLE_ENDIAN); |
| |
| int _getInt8(int offset) => _buffer.getInt8(offset); |
| |
| int _getUint16(int offset) => |
| _buffer.getUint16(offset, Endianness.LITTLE_ENDIAN); |
| |
| int _getUint32(int offset) => |
| _buffer.getUint32(offset, Endianness.LITTLE_ENDIAN); |
| |
| int _getUint8(int offset) => _buffer.getUint8(offset); |
| |
| /** |
| * If the [byteList] is already a [Uint8List] return it. |
| * Otherwise return a [Uint8List] copy of the [byteList]. |
| */ |
| static Uint8List _asUint8List(List<int> byteList) { |
| if (byteList is Uint8List) { |
| return byteList; |
| } else { |
| return new Uint8List.fromList(byteList); |
| } |
| } |
| } |
| |
| /** |
| * Class that helps building flat buffers. |
| */ |
| class Builder { |
| final int initialSize; |
| |
| /** |
| * The list of existing VTable(s). |
| */ |
| final List<_VTable> _vTables = <_VTable>[]; |
| |
| ByteData _buf; |
| |
| /** |
| * The maximum alignment that has been seen so far. If [_buf] has to be |
| * reallocated in the future (to insert room at its start for more bytes) the |
| * reallocation will need to be a multiple of this many bytes. |
| */ |
| int _maxAlign; |
| |
| /** |
| * The number of bytes that have been written to the buffer so far. The |
| * most recently written byte is this many bytes from the end of [_buf]. |
| */ |
| int _tail; |
| |
| /** |
| * The location of the end of the current table, measured in bytes from the |
| * end of [_buf], or `null` if a table is not currently being built. |
| */ |
| int _currentTableEndTail; |
| |
| _VTable _currentVTable; |
| |
| /** |
| * Map containing all strings that have been written so far. This allows us |
| * to avoid duplicating strings. |
| */ |
| Map<String, Offset<String>> _strings = <String, Offset<String>>{}; |
| |
| Builder({this.initialSize: 1024}) { |
| reset(); |
| } |
| |
| /** |
| * Add the [field] with the given boolean [value]. The field is not added if |
| * the [value] is equal to [def]. Booleans are stored as 8-bit fields with |
| * `0` for `false` and `1` for `true`. |
| */ |
| void addBool(int field, bool value, [bool def]) { |
| _ensureCurrentVTable(); |
| if (value != null && value != def) { |
| int size = 1; |
| _prepare(size, 1); |
| _trackField(field); |
| _buf.setInt8(_buf.lengthInBytes - _tail, value ? 1 : 0); |
| } |
| } |
| |
| /** |
| * Add the [field] with the given 32-bit signed integer [value]. The field is |
| * not added if the [value] is equal to [def]. |
| */ |
| void addInt32(int field, int value, [int def]) { |
| _ensureCurrentVTable(); |
| if (value != null && value != def) { |
| int size = 4; |
| _prepare(size, 1); |
| _trackField(field); |
| _setInt32AtTail(_buf, _tail, value); |
| } |
| } |
| |
| /** |
| * Add the [field] with the given 8-bit signed integer [value]. The field is |
| * not added if the [value] is equal to [def]. |
| */ |
| void addInt8(int field, int value, [int def]) { |
| _ensureCurrentVTable(); |
| if (value != null && value != def) { |
| int size = 1; |
| _prepare(size, 1); |
| _trackField(field); |
| _buf.setInt8(_buf.lengthInBytes - _tail, value); |
| } |
| } |
| |
| /** |
| * Add the [field] referencing an object with the given [offset]. |
| */ |
| void addOffset(int field, Offset offset) { |
| _ensureCurrentVTable(); |
| if (offset != null) { |
| _prepare(4, 1); |
| _trackField(field); |
| _setUint32AtTail(_buf, _tail, _tail - offset._tail); |
| } |
| } |
| |
| /** |
| * Add the [field] with the given 32-bit unsigned integer [value]. The field |
| * is not added if the [value] is equal to [def]. |
| */ |
| void addUint32(int field, int value, [int def]) { |
| _ensureCurrentVTable(); |
| if (value != null && value != def) { |
| int size = 4; |
| _prepare(size, 1); |
| _trackField(field); |
| _setUint32AtTail(_buf, _tail, value); |
| } |
| } |
| |
| /** |
| * Add the [field] with the given 8-bit unsigned integer [value]. The field |
| * is not added if the [value] is equal to [def]. |
| */ |
| void addUint8(int field, int value, [int def]) { |
| _ensureCurrentVTable(); |
| if (value != null && value != def) { |
| int size = 1; |
| _prepare(size, 1); |
| _trackField(field); |
| _setUint8AtTail(_buf, _tail, value); |
| } |
| } |
| |
| /** |
| * End the current table and return its offset. |
| */ |
| Offset endTable() { |
| if (_currentVTable == null) { |
| throw new StateError('Start a table before ending it.'); |
| } |
| // Prepare for writing the VTable. |
| _prepare(4, 1); |
| int tableTail = _tail; |
| // Prepare the size of the current table. |
| _currentVTable.tableSize = tableTail - _currentTableEndTail; |
| // Prepare the VTable to use for the current table. |
| int vTableTail; |
| { |
| _currentVTable.computeFieldOffsets(tableTail); |
| // Try to find an existing compatible VTable. |
| for (int i = 0; i < _vTables.length; i++) { |
| _VTable vTable = _vTables[i]; |
| if (_currentVTable.canUseExistingVTable(vTable)) { |
| vTableTail = vTable.tail; |
| break; |
| } |
| } |
| // Write a new VTable. |
| if (vTableTail == null) { |
| _prepare(2, _currentVTable.numOfUint16); |
| vTableTail = _tail; |
| _currentVTable.tail = vTableTail; |
| _currentVTable.output(_buf, _buf.lengthInBytes - _tail); |
| _vTables.add(_currentVTable); |
| } |
| } |
| // Set the VTable offset. |
| _setInt32AtTail(_buf, tableTail, vTableTail - tableTail); |
| // Done with this table. |
| _currentVTable = null; |
| return new Offset(tableTail); |
| } |
| |
| /** |
| * Finish off the creation of the buffer. The given [offset] is used as the |
| * root object offset, and usually references directly or indirectly every |
| * written object. If [fileIdentifier] is specified (and not `null`), it is |
| * interpreted as a 4-byte Latin-1 encoded string that should be placed at |
| * bytes 4-7 of the file. |
| */ |
| Uint8List finish(Offset offset, [String fileIdentifier]) { |
| _prepare(max(4, _maxAlign), fileIdentifier == null ? 1 : 2); |
| int alignedTail = _tail + ((-_tail) % _maxAlign); |
| _setUint32AtTail(_buf, alignedTail, alignedTail - offset._tail); |
| if (fileIdentifier != null) { |
| for (int i = 0; i < 4; i++) { |
| _setUint8AtTail( |
| _buf, alignedTail - 4 - i, fileIdentifier.codeUnitAt(i)); |
| } |
| } |
| return _buf.buffer.asUint8List(_buf.lengthInBytes - alignedTail); |
| } |
| |
| /** |
| * This is a low-level method, it should not be invoked by clients. |
| */ |
| Uint8List lowFinish() { |
| int alignedTail = _tail + ((-_tail) % _maxAlign); |
| return _buf.buffer.asUint8List(_buf.lengthInBytes - alignedTail); |
| } |
| |
| /** |
| * This is a low-level method, it should not be invoked by clients. |
| */ |
| void lowReset() { |
| _buf = new ByteData(initialSize); |
| _maxAlign = 1; |
| _tail = 0; |
| } |
| |
| /** |
| * This is a low-level method, it should not be invoked by clients. |
| */ |
| void lowWriteUint32(int value) { |
| _prepare(4, 1); |
| _setUint32AtTail(_buf, _tail, value); |
| } |
| |
| /** |
| * This is a low-level method, it should not be invoked by clients. |
| */ |
| void lowWriteUint8(int value) { |
| _prepare(1, 1); |
| _buf.setUint8(_buf.lengthInBytes - _tail, value); |
| } |
| |
| /** |
| * Reset the builder and make it ready for filling a new buffer. |
| */ |
| void reset() { |
| _buf = new ByteData(initialSize); |
| _maxAlign = 1; |
| _tail = 0; |
| _currentVTable = null; |
| } |
| |
| /** |
| * Start a new table. Must be finished with [endTable] invocation. |
| */ |
| void startTable() { |
| if (_currentVTable != null) { |
| throw new StateError('Inline tables are not supported.'); |
| } |
| _currentVTable = new _VTable(); |
| _currentTableEndTail = _tail; |
| } |
| |
| /** |
| * Write the given list of [values]. |
| */ |
| Offset writeList(List<Offset> values) { |
| _ensureNoVTable(); |
| _prepare(4, 1 + values.length); |
| Offset result = new Offset(_tail); |
| int tail = _tail; |
| _setUint32AtTail(_buf, tail, values.length); |
| tail -= 4; |
| for (Offset value in values) { |
| _setUint32AtTail(_buf, tail, tail - value._tail); |
| tail -= 4; |
| } |
| return result; |
| } |
| |
| /** |
| * Write the given list of boolean [values]. |
| */ |
| Offset writeListBool(List<bool> values) { |
| int bitLength = values.length; |
| int padding = (-bitLength) % 8; |
| int byteLength = (bitLength + padding) ~/ 8; |
| // Prepare the backing Uint8List. |
| Uint8List bytes = new Uint8List(byteLength + 1); |
| // Record every bit. |
| int byteIndex = 0; |
| int byte = 0; |
| int mask = 1; |
| for (int bitIndex = 0; bitIndex < bitLength; bitIndex++) { |
| if (bitIndex != 0 && (bitIndex % 8 == 0)) { |
| bytes[byteIndex++] = byte; |
| byte = 0; |
| mask = 1; |
| } |
| if (values[bitIndex]) { |
| byte |= mask; |
| } |
| mask <<= 1; |
| } |
| // Write the last byte, even if it may be on the padding. |
| bytes[byteIndex] = byte; |
| // Write the padding length. |
| bytes[byteLength] = padding; |
| // Write as a Uint8 list. |
| return writeListUint8(bytes); |
| } |
| |
| /** |
| * Write the given list of 64-bit float [values]. |
| */ |
| Offset writeListFloat64(List<double> values) { |
| _ensureNoVTable(); |
| _prepare(8, 1 + values.length); |
| Offset result = new Offset(_tail); |
| int tail = _tail; |
| _setUint32AtTail(_buf, tail, values.length); |
| tail -= 8; |
| for (double value in values) { |
| _setFloat64AtTail(_buf, tail, value); |
| tail -= 8; |
| } |
| return result; |
| } |
| |
| /** |
| * Write the given list of signed 32-bit integer [values]. |
| */ |
| Offset writeListInt32(List<int> values) { |
| _ensureNoVTable(); |
| _prepare(4, 1 + values.length); |
| Offset result = new Offset(_tail); |
| int tail = _tail; |
| _setUint32AtTail(_buf, tail, values.length); |
| tail -= 4; |
| for (int value in values) { |
| _setInt32AtTail(_buf, tail, value); |
| tail -= 4; |
| } |
| return result; |
| } |
| |
| /** |
| * Write the given list of unsigned 32-bit integer [values]. |
| */ |
| Offset writeListUint32(List<int> values) { |
| _ensureNoVTable(); |
| _prepare(4, 1 + values.length); |
| Offset result = new Offset(_tail); |
| int tail = _tail; |
| _setUint32AtTail(_buf, tail, values.length); |
| tail -= 4; |
| for (int value in values) { |
| _setUint32AtTail(_buf, tail, value); |
| tail -= 4; |
| } |
| return result; |
| } |
| |
| /** |
| * Write the given list of unsigned 8-bit integer [values]. |
| */ |
| Offset writeListUint8(List<int> values) { |
| _ensureNoVTable(); |
| _prepare(4, 1, additionalBytes: values.length); |
| Offset result = new Offset(_tail); |
| int tail = _tail; |
| _setUint32AtTail(_buf, tail, values.length); |
| tail -= 4; |
| for (int value in values) { |
| _setUint8AtTail(_buf, tail, value); |
| tail -= 1; |
| } |
| return result; |
| } |
| |
| /** |
| * Write the given string [value] and return its [Offset], or `null` if |
| * the [value] is equal to [def]. |
| */ |
| Offset<String> writeString(String value, [String def]) { |
| _ensureNoVTable(); |
| if (value != def) { |
| return _strings.putIfAbsent(value, () { |
| // TODO(scheglov) optimize for ASCII strings |
| List<int> bytes = UTF8.encode(value); |
| int length = bytes.length; |
| _prepare(4, 1, additionalBytes: length); |
| Offset<String> result = new Offset(_tail); |
| _setUint32AtTail(_buf, _tail, length); |
| int offset = _buf.lengthInBytes - _tail + 4; |
| for (int i = 0; i < length; i++) { |
| _buf.setUint8(offset++, bytes[i]); |
| } |
| return result; |
| }); |
| } |
| return null; |
| } |
| |
| /** |
| * Throw an exception if there is not currently a vtable. |
| */ |
| void _ensureCurrentVTable() { |
| if (_currentVTable == null) { |
| throw new StateError('Start a table before adding values.'); |
| } |
| } |
| |
| /** |
| * Throw an exception if there is currently a vtable. |
| */ |
| void _ensureNoVTable() { |
| if (_currentVTable != null) { |
| throw new StateError( |
| 'Cannot write a non-scalar value while writing a table.'); |
| } |
| } |
| |
| /** |
| * Prepare for writing the given [count] of scalars of the given [size]. |
| * Additionally allocate the specified [additionalBytes]. Update the current |
| * tail pointer to point at the allocated space. |
| */ |
| void _prepare(int size, int count, {int additionalBytes: 0}) { |
| // Update the alignment. |
| if (_maxAlign < size) { |
| _maxAlign = size; |
| } |
| // Prepare amount of required space. |
| int dataSize = size * count + additionalBytes; |
| int alignDelta = (-(_tail + dataSize)) % size; |
| int bufSize = alignDelta + dataSize; |
| // Ensure that we have the required amount of space. |
| { |
| int oldCapacity = _buf.lengthInBytes; |
| if (_tail + bufSize > oldCapacity) { |
| int desiredNewCapacity = (oldCapacity + bufSize) * 2; |
| int deltaCapacity = desiredNewCapacity - oldCapacity; |
| deltaCapacity += (-deltaCapacity) % _maxAlign; |
| int newCapacity = oldCapacity + deltaCapacity; |
| ByteData newBuf = new ByteData(newCapacity); |
| newBuf.buffer |
| .asUint8List() |
| .setAll(deltaCapacity, _buf.buffer.asUint8List()); |
| _buf = newBuf; |
| } |
| } |
| // Update the tail pointer. |
| _tail += bufSize; |
| } |
| |
| /** |
| * Record the offset of the given [field]. |
| */ |
| void _trackField(int field) { |
| _currentVTable.addField(field, _tail); |
| } |
| |
| static void _setFloat64AtTail(ByteData _buf, int tail, double x) { |
| _buf.setFloat64(_buf.lengthInBytes - tail, x, Endianness.LITTLE_ENDIAN); |
| } |
| |
| static void _setInt32AtTail(ByteData _buf, int tail, int x) { |
| _buf.setInt32(_buf.lengthInBytes - tail, x, Endianness.LITTLE_ENDIAN); |
| } |
| |
| static void _setUint32AtTail(ByteData _buf, int tail, int x) { |
| _buf.setUint32(_buf.lengthInBytes - tail, x, Endianness.LITTLE_ENDIAN); |
| } |
| |
| static void _setUint8AtTail(ByteData _buf, int tail, int x) { |
| _buf.setUint8(_buf.lengthInBytes - tail, x); |
| } |
| } |
| |
| /** |
| * The reader of lists of 64-bit float values. |
| * |
| * The returned unmodifiable lists lazily read values on access. |
| */ |
| class Float64ListReader extends Reader<List<double>> { |
| const Float64ListReader(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| List<double> read(BufferContext bc, int offset) => |
| new _FbFloat64List(bc, bc.derefObject(offset)); |
| } |
| |
| /** |
| * The reader of signed 32-bit integers. |
| */ |
| class Int32Reader extends Reader<int> { |
| const Int32Reader() : super(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| int read(BufferContext bc, int offset) => bc._getInt32(offset); |
| } |
| |
| /** |
| * The reader of 8-bit signed integers. |
| */ |
| class Int8Reader extends Reader<int> { |
| const Int8Reader() : super(); |
| |
| @override |
| int get size => 1; |
| |
| @override |
| int read(BufferContext bc, int offset) => bc._getInt8(offset); |
| } |
| |
| /** |
| * The reader of lists of objects. |
| * |
| * The returned unmodifiable lists lazily read objects on access. |
| */ |
| class ListReader<E> extends Reader<List<E>> { |
| final Reader<E> _elementReader; |
| |
| const ListReader(this._elementReader); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| List<E> read(BufferContext bc, int offset) => |
| new _FbGenericList<E>(_elementReader, bc, bc.derefObject(offset)); |
| } |
| |
| /** |
| * The offset from the end of the buffer to a serialized object of the type [T]. |
| */ |
| class Offset<T> { |
| final int _tail; |
| |
| Offset(this._tail); |
| } |
| |
| /** |
| * Object that can read a value at a [BufferContext]. |
| */ |
| abstract class Reader<T> { |
| const Reader(); |
| |
| /** |
| * The size of the value in bytes. |
| */ |
| int get size; |
| |
| /** |
| * Read the value at the given [offset] in [bc]. |
| */ |
| T read(BufferContext bc, int offset); |
| |
| /** |
| * Read the value of the given [field] in the given [object]. |
| */ |
| T vTableGet(BufferContext object, int offset, int field, [T defaultValue]) { |
| int vTableSOffset = object._getInt32(offset); |
| int vTableOffset = offset - vTableSOffset; |
| int vTableSize = object._getUint16(vTableOffset); |
| int vTableFieldOffset = (1 + 1 + field) * 2; |
| if (vTableFieldOffset < vTableSize) { |
| int fieldOffsetInObject = |
| object._getUint16(vTableOffset + vTableFieldOffset); |
| if (fieldOffsetInObject != 0) { |
| return read(object, offset + fieldOffsetInObject); |
| } |
| } |
| return defaultValue; |
| } |
| } |
| |
| /** |
| * The reader of string values. |
| */ |
| class StringReader extends Reader<String> { |
| const StringReader() : super(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| String read(BufferContext bc, int offset) { |
| int strOffset = bc.derefObject(offset); |
| int length = bc._getUint32(strOffset); |
| Uint8List bytes = bc._asUint8LIst(strOffset + 4, length); |
| if (_isLatin(bytes)) { |
| return new String.fromCharCodes(bytes); |
| } |
| return UTF8.decode(bytes); |
| } |
| |
| static bool _isLatin(Uint8List bytes) { |
| int length = bytes.length; |
| for (int i = 0; i < length; i++) { |
| if (bytes[i] > 127) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * An abstract reader for tables. |
| */ |
| abstract class TableReader<T> extends Reader<T> { |
| const TableReader(); |
| |
| @override |
| int get size => 4; |
| |
| /** |
| * Return the object at [offset]. |
| */ |
| T createObject(BufferContext bc, int offset); |
| |
| @override |
| T read(BufferContext bp, int offset) { |
| int objectOffset = bp.derefObject(offset); |
| return createObject(bp, objectOffset); |
| } |
| } |
| |
| /** |
| * Reader of lists of unsigned 32-bit integer values. |
| * |
| * The returned unmodifiable lists lazily read values on access. |
| */ |
| class Uint32ListReader extends Reader<List<int>> { |
| const Uint32ListReader(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| List<int> read(BufferContext bc, int offset) => |
| new _FbUint32List(bc, bc.derefObject(offset)); |
| } |
| |
| /** |
| * The reader of unsigned 32-bit integers. |
| */ |
| class Uint32Reader extends Reader<int> { |
| const Uint32Reader() : super(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| int read(BufferContext bc, int offset) => bc._getUint32(offset); |
| } |
| |
| /** |
| * Reader of lists of unsigned 8-bit integer values. |
| * |
| * The returned unmodifiable lists lazily read values on access. |
| */ |
| class Uint8ListReader extends Reader<List<int>> { |
| const Uint8ListReader(); |
| |
| @override |
| int get size => 4; |
| |
| @override |
| List<int> read(BufferContext bc, int offset) => |
| new _FbUint8List(bc, bc.derefObject(offset)); |
| } |
| |
| /** |
| * The reader of unsigned 8-bit integers. |
| */ |
| class Uint8Reader extends Reader<int> { |
| const Uint8Reader() : super(); |
| |
| @override |
| int get size => 1; |
| |
| @override |
| int read(BufferContext bc, int offset) => bc._getUint8(offset); |
| } |
| |
| /** |
| * List of booleans backed by 8-bit unsigned integers. |
| */ |
| class _FbBoolList extends Object with ListMixin<bool> implements List<bool> { |
| final BufferContext bc; |
| final int offset; |
| int _length; |
| |
| _FbBoolList(this.bc, this.offset); |
| |
| @override |
| int get length { |
| if (_length == null) { |
| int byteLength = bc._getUint32(offset); |
| _length = (byteLength - 1) * 8 - _getByte(byteLength - 1); |
| } |
| return _length; |
| } |
| |
| @override |
| void set length(int i) => |
| throw new StateError('Attempt to modify immutable list'); |
| |
| @override |
| bool operator [](int i) { |
| int index = i ~/ 8; |
| int mask = 1 << i % 8; |
| return _getByte(index) & mask != 0; |
| } |
| |
| @override |
| void operator []=(int i, bool e) => |
| throw new StateError('Attempt to modify immutable list'); |
| |
| int _getByte(int index) => bc._getUint8(offset + 4 + index); |
| } |
| |
| /** |
| * The list backed by 64-bit values - Uint64 length and Float64. |
| */ |
| class _FbFloat64List extends _FbList<double> { |
| _FbFloat64List(BufferContext bc, int offset) : super(bc, offset); |
| |
| @override |
| double operator [](int i) { |
| return bc._getFloat64(offset + 8 + 8 * i); |
| } |
| } |
| |
| /** |
| * List backed by a generic object which may have any size. |
| */ |
| class _FbGenericList<E> extends _FbList<E> { |
| final Reader<E> elementReader; |
| |
| List<E> _items; |
| |
| _FbGenericList(this.elementReader, BufferContext bp, int offset) |
| : super(bp, offset); |
| |
| @override |
| E operator [](int i) { |
| _items ??= new List<E>(length); |
| E item = _items[i]; |
| if (item == null) { |
| item = elementReader.read(bc, offset + 4 + elementReader.size * i); |
| _items[i] = item; |
| } |
| return item; |
| } |
| } |
| |
| /** |
| * The base class for immutable lists read from flat buffers. |
| */ |
| abstract class _FbList<E> extends Object with ListMixin<E> implements List<E> { |
| final BufferContext bc; |
| final int offset; |
| int _length; |
| |
| _FbList(this.bc, this.offset); |
| |
| @override |
| int get length { |
| _length ??= bc._getUint32(offset); |
| return _length; |
| } |
| |
| @override |
| void set length(int i) => |
| throw new StateError('Attempt to modify immutable list'); |
| |
| @override |
| void operator []=(int i, E e) => |
| throw new StateError('Attempt to modify immutable list'); |
| } |
| |
| /** |
| * List backed by 32-bit unsigned integers. |
| */ |
| class _FbUint32List extends _FbList<int> { |
| _FbUint32List(BufferContext bc, int offset) : super(bc, offset); |
| |
| @override |
| int operator [](int i) { |
| return bc._getUint32(offset + 4 + 4 * i); |
| } |
| } |
| |
| /** |
| * List backed by 8-bit unsigned integers. |
| */ |
| class _FbUint8List extends _FbList<int> { |
| _FbUint8List(BufferContext bc, int offset) : super(bc, offset); |
| |
| @override |
| int operator [](int i) { |
| return bc._getUint8(offset + 4 + i); |
| } |
| } |
| |
| /** |
| * Class that describes the structure of a table. |
| */ |
| class _VTable { |
| final List<int> fieldTails = <int>[]; |
| final List<int> fieldOffsets = <int>[]; |
| |
| /** |
| * The size of the table that uses this VTable. |
| */ |
| int tableSize; |
| |
| /** |
| * The tail of this VTable. It is used to share the same VTable between |
| * multiple tables of identical structure. |
| */ |
| int tail; |
| |
| int get numOfUint16 => 1 + 1 + fieldTails.length; |
| |
| void addField(int field, int offset) { |
| while (fieldTails.length <= field) { |
| fieldTails.add(null); |
| } |
| fieldTails[field] = offset; |
| } |
| |
| /** |
| * Return `true` if the [existing] VTable can be used instead of this. |
| */ |
| bool canUseExistingVTable(_VTable existing) { |
| assert(tail == null); |
| assert(existing.tail != null); |
| if (tableSize == existing.tableSize && |
| fieldOffsets.length == existing.fieldOffsets.length) { |
| for (int i = 0; i < fieldOffsets.length; i++) { |
| if (fieldOffsets[i] != existing.fieldOffsets[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Fill the [fieldOffsets] field. |
| */ |
| void computeFieldOffsets(int tableTail) { |
| assert(fieldOffsets.isEmpty); |
| for (int fieldTail in fieldTails) { |
| int fieldOffset = fieldTail == null ? 0 : tableTail - fieldTail; |
| fieldOffsets.add(fieldOffset); |
| } |
| } |
| |
| /** |
| * Outputs this VTable to [buf], which is is expected to be aligned to 16-bit |
| * and have at least [numOfUint16] 16-bit words available. |
| */ |
| void output(ByteData buf, int bufOffset) { |
| // VTable size. |
| buf.setUint16(bufOffset, numOfUint16 * 2, Endianness.LITTLE_ENDIAN); |
| bufOffset += 2; |
| // Table size. |
| buf.setUint16(bufOffset, tableSize, Endianness.LITTLE_ENDIAN); |
| bufOffset += 2; |
| // Field offsets. |
| for (int fieldOffset in fieldOffsets) { |
| buf.setUint16(bufOffset, fieldOffset, Endianness.LITTLE_ENDIAN); |
| bufOffset += 2; |
| } |
| } |
| } |