| // Copyright (c) 2022, 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. |
| |
| import '../serialization/serialization.dart'; |
| |
| /// A canonicalized record shape comprising zero or more positional fields |
| /// followed by zero or more named fields in sorted order. |
| /// |
| /// A RecordShape is used in conjunction with a simple [List] of length |
| /// [fieldCount] to represent data about a record, for example, the types or |
| /// values of the fields. Unnamed (indexed) fields correspond to the same index |
| /// in the List. Named fields follow the unnamed fields in the canonical |
| /// (sorted) order. [RecordShape.indexOfFieldName] can be used to find the index |
| /// corresponding to a name. |
| class RecordShape { |
| /// Tag used for identifying serialized [RecordShape] objects in a debugging |
| /// data stream. |
| static const String tag = 'record-shape'; |
| |
| /// Number of positional fields. |
| final int positionalFieldCount; |
| |
| /// Names of named fields in canonical order. |
| final List<String> fieldNames; |
| |
| int get namedFieldCount => fieldNames.length; |
| |
| int get fieldCount => positionalFieldCount + namedFieldCount; |
| |
| RecordShape._(this.positionalFieldCount, [this.fieldNames = const []]); |
| |
| static String positionalFieldIndexToGetterName(int i) { |
| return '\$${i + 1}'; |
| } |
| |
| factory RecordShape(int positionalFieldCount, List<String> names) { |
| assert(positionalFieldCount >= 0); |
| if (names.isEmpty) { |
| if (0 <= positionalFieldCount && positionalFieldCount < _common.length) { |
| return _common[positionalFieldCount]; |
| } |
| return RecordShape._(positionalFieldCount, const []); |
| } |
| assert(_isSorted(names)); |
| return RecordShape._(positionalFieldCount, names); |
| } |
| |
| static bool _isSorted(List<String> names) { |
| for (int i = 1; i < names.length; i++) { |
| if (names[i - 1].compareTo(names[i]) >= 0) return false; |
| } |
| return true; |
| } |
| |
| static final List<RecordShape> _common = [ |
| RecordShape._(0), |
| RecordShape._(1), |
| RecordShape._(2), |
| RecordShape._(3), |
| RecordShape._(4), |
| RecordShape._(5), |
| ]; |
| |
| /// Deserializes a [RecordShape] object from [source]. |
| factory RecordShape.readFromDataSource(DataSourceReader source) { |
| source.begin(tag); |
| int positionals = source.readInt(); |
| List<String> names = source.readStrings(); |
| source.end(tag); |
| return RecordShape(positionals, names); |
| } |
| |
| /// Serializes this [RecordShape] to [sink]. |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| sink.writeInt(positionalFieldCount); |
| sink.writeStrings(fieldNames); |
| sink.end(tag); |
| } |
| |
| /// Ordering for shapes. Shapes with fewer fields sort before shapes with more |
| /// fields. Shapes with the same number of fields are ordered |
| /// lexicographically, with unnamed fields coming before named fields. |
| static int compare(RecordShape a, RecordShape b) { |
| // Group by total field count, smaller shapes first. |
| int r = a.fieldCount.compareTo(b.fieldCount); |
| if (r != 0) return r; |
| final aNames = a.fieldNames; |
| final bNames = b.fieldNames; |
| r = aNames.length.compareTo(bNames.length); |
| if (r != 0) return r; |
| for (int i = 0; i < aNames.length; i++) { |
| r = aNames[i].compareTo(bNames[i]); |
| if (r != 0) return r; |
| } |
| assert(a == b); |
| return 0; |
| } |
| |
| int indexOfFieldName(String name) { |
| int nameIndex = fieldNames.indexOf(name); |
| if (nameIndex < 0) throw ArgumentError.value(name, 'name'); |
| return positionalFieldCount + nameIndex; |
| } |
| |
| int indexOfGetterName(String name) { |
| int nameIndex = fieldNames.indexOf(name); |
| if (nameIndex < 0) { |
| if (name[0] == '\$') { |
| final position = int.tryParse(name.substring(1)); |
| if (position != null && position <= positionalFieldCount) { |
| return position - 1; |
| } |
| } |
| return -1; |
| } |
| return positionalFieldCount + nameIndex; |
| } |
| |
| String getterNameOfIndex(int index) => |
| index < positionalFieldCount |
| ? positionalFieldIndexToGetterName(index) |
| : fieldNames[index - positionalFieldCount]; |
| |
| bool nameMatchesGetter(String name) { |
| return indexOfGetterName(name) >= 0; |
| } |
| |
| @override |
| bool operator ==(Object other) => |
| identical(this, other) || other is RecordShape && _sameShape(this, other); |
| |
| @override |
| late final int hashCode = _hashCode(); |
| |
| int _hashCode() { |
| return Object.hash(positionalFieldCount, Object.hashAll(fieldNames)); |
| } |
| |
| static bool _sameShape(RecordShape a, RecordShape b) { |
| if (a.positionalFieldCount != b.positionalFieldCount) return false; |
| if (a.fieldNames.length != b.fieldNames.length) return false; |
| for (int i = 0; i < a.fieldNames.length; i++) { |
| if (a.fieldNames[i] != b.fieldNames[i]) return false; |
| } |
| return true; |
| } |
| |
| @override |
| String toString() { |
| if (fieldNames.isEmpty) { |
| return 'RecordShape($positionalFieldCount)'; |
| } |
| return 'RecordShape($positionalFieldCount, {${fieldNames.join(", ")}})'; |
| } |
| } |