blob: 9448fecae105d1fd5f814f61b4c898c5c00557ee [file] [log] [blame]
// Copyright (c) 2015, 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.
part of protobuf;
typedef void FrozenMessageErrorHandler(String messageName, [String methodName]);
void defaultFrozenMessageModificationHandler(String messageName,
[String methodName]) {
if (methodName != null) {
throw new UnsupportedError(
"Attempted to call $methodName on a read-only message ($messageName)");
}
throw new UnsupportedError(
"Attempted to change a read-only message ($messageName)");
}
/// Invoked when an attempt is made to modify a frozen message.
///
/// This handler can log the attempt, throw an exception, or ignore the attempt
/// altogether.
///
/// If the handler returns normally, the modification is allowed, and execution
/// proceeds as if the message was writable.
FrozenMessageErrorHandler frozenMessageModificationHandler =
defaultFrozenMessageModificationHandler;
/// All the data in a GeneratedMessage.
///
/// These fields and methods are in a separate class to avoid
/// polymorphic access due to inheritance. This turns out to
/// be faster when compiled to JavaScript.
class _FieldSet {
final GeneratedMessage _message;
final BuilderInfo _meta;
final EventPlugin _eventPlugin;
bool _isReadOnly = false;
/// The value of each non-extension field in a fixed-length array.
/// The index of a field can be found in [FieldInfo.index].
/// A null entry indicates that the field has no value.
final List _values;
/// Contains all the extension fields, or null if there aren't any.
_ExtensionFieldSet _extensions;
/// Contains all the unknown fields, or null if there aren't any.
UnknownFieldSet _unknownFields;
_FieldSet(this._message, BuilderInfo meta, this._eventPlugin)
: this._meta = meta,
_values = _makeValueList(meta.byIndex.length);
static _makeValueList(int length) {
if (length == 0) return _zeroList;
return new List(length);
}
// Use a fixed length list and not a constant list to ensure that _values
// always has the same implementation type.
static List _zeroList = new List(0);
// Metadata about multiple fields
String get _messageName => _meta.messageName;
bool get _hasRequiredFields => _meta.hasRequiredFields;
/// The FieldInfo for each non-extension field.
Iterable<FieldInfo> get _infos => _meta.fieldInfo.values;
/// The FieldInfo for each non-extension field in tag order.
Iterable<FieldInfo> get _infosSortedByTag => _meta.sortedByTag;
/// Returns true if we should send events to the plugin.
bool get _hasObservers => _eventPlugin != null && _eventPlugin.hasObservers;
bool get _hasExtensions => _extensions != null;
bool get hasUnknownFields => _unknownFields != null;
_ExtensionFieldSet _ensureExtensions() {
assert(!_isReadOnly);
if (!_hasExtensions) _extensions = new _ExtensionFieldSet(this);
return _extensions;
}
UnknownFieldSet _ensureUnknownFields() {
if (_isReadOnly) return _ReadonlyUnknownFieldSet._empty;
if (_unknownFields == null) _unknownFields = new UnknownFieldSet();
return _unknownFields;
}
// Metadata about single fields
/// Returns FieldInfo for a non-extension field, or null if not found.
FieldInfo _nonExtensionInfo(int tagNumber) => _meta.fieldInfo[tagNumber];
/// Returns FieldInfo for a non-extension field.
FieldInfo _nonExtensionInfoByIndex(int index) => _meta.byIndex[index];
/// Returns the FieldInfo for a regular or extension field.
/// throws ArgumentException if no info is found.
FieldInfo _ensureInfo(int tagNumber) {
var fi = _getFieldInfoOrNull(tagNumber);
if (fi != null) return fi;
throw new ArgumentError("tag $tagNumber not defined in $_messageName");
}
/// Returns the FieldInfo for a regular or extension field.
FieldInfo _getFieldInfoOrNull(int tagNumber) {
var fi = _nonExtensionInfo(tagNumber);
if (fi != null) return fi;
if (!_hasExtensions) return null;
return _extensions._getInfoOrNull(tagNumber);
}
void _markReadOnly() {
if (_isReadOnly) return;
_isReadOnly = true;
for (var field in _meta.sortedByTag) {
if (field.isRepeated) {
final entries = _values[field.index];
if (entries == null) continue;
if (field.isGroupOrMessage) {
for (var subMessage in entries as List<GeneratedMessage>) {
subMessage.freeze();
}
}
_values[field.index] = entries.toFrozenPbList();
} else if (field.isGroupOrMessage) {
final entry = _values[field.index];
if (entry != null) {
(entry as GeneratedMessage).freeze();
}
}
}
}
void _ensureWritable() {
if (_isReadOnly) frozenMessageModificationHandler(_messageName);
}
// Single-field operations
/// Gets a field with full error-checking.
///
/// Works for both extended and non-extended fields.
/// Creates repeated fields (unless read-only).
/// Suitable for public API.
_getField(int tagNumber) {
var fi = _nonExtensionInfo(tagNumber);
if (fi != null) {
var value = _values[fi.index];
if (value != null) return value;
return _getDefault(fi);
}
if (_hasExtensions) {
var fi = _extensions._getInfoOrNull(tagNumber);
if (fi != null) {
return _extensions._getFieldOrDefault(fi);
}
}
throw new ArgumentError("tag $tagNumber not defined in $_messageName");
}
_getDefault(FieldInfo fi) {
if (!fi.isRepeated) return fi.makeDefault();
if (_isReadOnly) return fi.readonlyDefault;
// TODO(skybrian) we could avoid this by generating another
// method for repeated fields:
// msg.mutableFoo().add(123);
var value = fi._createRepeatedField(_message);
_setNonExtensionFieldUnchecked(fi, value);
return value;
}
List<T> _getDefaultList<T>(FieldInfo<T> fi) {
assert(fi.isRepeated);
if (_isReadOnly) return new List.unmodifiable(const []);
// TODO(skybrian) we could avoid this by generating another
// method for repeated fields:
// msg.mutableFoo().add(123);
var value = fi._createRepeatedFieldWithType<T>(_message);
_setNonExtensionFieldUnchecked(fi, value);
return value;
}
_getFieldOrNullByTag(int tagNumber) {
var fi = _getFieldInfoOrNull(tagNumber);
if (fi == null) return null;
return _getFieldOrNull(fi);
}
/// Returns the field's value or null if not set.
///
/// Works for both extended and non-extend fields.
/// Works for both repeated and non-repeated fields.
_getFieldOrNull(FieldInfo fi) {
if (fi.index != null) return _values[fi.index];
if (!_hasExtensions) return null;
return _extensions._getFieldOrNull(fi);
}
bool _hasField(int tagNumber) {
var fi = _nonExtensionInfo(tagNumber);
if (fi != null) return _$has(fi.index);
if (!_hasExtensions) return false;
return _extensions._hasField(tagNumber);
}
void _clearField(int tagNumber) {
_ensureWritable();
var fi = _nonExtensionInfo(tagNumber);
if (fi != null) {
// clear a non-extension field
if (_hasObservers) _eventPlugin.beforeClearField(fi);
_values[fi.index] = null;
return;
}
if (_hasExtensions) {
var fi = _extensions._getInfoOrNull(tagNumber);
if (fi != null) {
_extensions._clearField(fi);
return;
}
}
// neither a regular field nor an extension.
// TODO(skybrian) throw?
}
/// Sets a non-repeated field with error-checking.
///
/// Works for both extended and non-extended fields.
/// Suitable for public API.
void _setField(int tagNumber, value) {
if (value == null) throw new ArgumentError('value is null');
var fi = _nonExtensionInfo(tagNumber);
if (fi == null) {
if (!_hasExtensions) {
throw new ArgumentError("tag $tagNumber not defined in $_messageName");
}
_extensions._setField(tagNumber, value);
return;
}
if (fi.isRepeated) {
throw new ArgumentError(_setFieldFailedMessage(
fi, value, 'repeating field (use get + .add())'));
}
_validateField(fi, value);
_setNonExtensionFieldUnchecked(fi, value);
}
/// Sets a non-repeated field without validating it.
///
/// Works for both extended and non-extended fields.
/// Suitable for decoders that do their own validation.
void _setFieldUnchecked(FieldInfo fi, value) {
assert(fi != null);
assert(!fi.isRepeated);
if (fi.index == null) {
_ensureExtensions()
.._addInfoUnchecked(fi)
.._setFieldUnchecked(fi, value);
} else {
_setNonExtensionFieldUnchecked(fi, value);
}
}
/// Returns the list to use for adding to a repeated field.
///
/// Works for both extended and non-extended fields.
/// Creates and stores the repeated field if it doesn't exist.
/// If it's an extension and the list doesn't exist, validates and stores it.
/// Suitable for decoders.
List<T> _ensureRepeatedField<T>(FieldInfo<T> fi) {
assert(!_isReadOnly);
assert(fi.isRepeated);
if (fi.index == null) {
return _ensureExtensions()._ensureRepeatedField(fi);
}
var value = _getFieldOrNull(fi);
if (value != null) return value as List<T>;
var newValue = fi._createRepeatedField(_message);
_setNonExtensionFieldUnchecked(fi, newValue);
return newValue;
}
/// Sets a non-extended field and fires events.
void _setNonExtensionFieldUnchecked(FieldInfo fi, value) {
if (_hasObservers) {
_eventPlugin.beforeSetField(fi, value);
}
_values[fi.index] = value;
}
// Generated method implementations
/// The implementation of a generated getter.
T _$get<T>(int index, T defaultValue) {
var value = _values[index];
if (value != null) return value as T;
if (defaultValue != null) return defaultValue;
return _getDefault(_nonExtensionInfoByIndex(index)) as T;
}
/// The implementation of a generated getter. Common case for submessages.
T _$getN<T>(int index) {
var value = _values[index];
if (value != null) return value as T;
return _getDefault(_nonExtensionInfoByIndex(index)) as T;
}
/// The implementation of a generated getter for repeated fields.
List<T> _$getList<T>(int index) {
var value = _values[index];
if (value != null) return value as List<T>;
return _getDefaultList<T>(_nonExtensionInfoByIndex(index));
}
/// The implementation of a generated getter for String fields.
String _$getS(int index, String defaultValue) {
var value = _values[index];
if (value == null) {
if (defaultValue != null) return defaultValue;
value = _getDefault(_nonExtensionInfoByIndex(index));
}
String result = value;
return result;
}
/// The implementation of a generated getter for Int64 fields.
Int64 _$getI64(int index) {
var value = _values[index];
if (value == null) {
value = _getDefault(_nonExtensionInfoByIndex(index));
}
Int64 result = value;
return result;
}
/// The implementation of a generated 'has' method.
bool _$has(int index) {
var value = _values[index];
if (value == null) return false;
if (value is List) return value.isNotEmpty;
return true;
}
/// The implementation of a generated setter.
///
/// In production, does no validation other than a null check.
/// Only handles non-repeated, non-extension fields.
/// Also, doesn't handle enums or messages which need per-type validation.
void _$set(int index, value) {
assert(!_nonExtensionInfoByIndex(index).isRepeated);
assert(_$check(index, value));
_ensureWritable();
if (value == null) {
_$check(index, value); // throw exception for null value
}
if (_hasObservers) {
_eventPlugin.beforeSetField(_nonExtensionInfoByIndex(index), value);
}
_values[index] = value;
}
bool _$check(int index, var newValue) {
_validateField(_nonExtensionInfoByIndex(index), newValue);
return true; // Allows use in an assertion.
}
// Bulk operations reading or writing multiple fields
void _clear() {
_ensureWritable();
if (_unknownFields != null) {
_unknownFields.clear();
}
if (_hasObservers) {
for (var fi in _infos) {
if (_values[fi.index] != null) {
_eventPlugin.beforeClearField(fi);
}
}
if (_hasExtensions) {
for (int key in _extensions._tagNumbers) {
var fi = _extensions._getInfoOrNull(key);
_eventPlugin.beforeClearField(fi);
}
}
}
if (_values.isNotEmpty) _values.fillRange(0, _values.length, null);
if (_hasExtensions) _extensions._clearValues();
}
bool _equals(_FieldSet o) {
if (_meta != o._meta) return false;
for (var i = 0; i < _values.length; i++) {
if (!_equalFieldValues(_values[i], o._values[i])) return false;
}
if (!_hasExtensions || !_extensions._hasValues) {
// Check if other extensions are logically empty.
// (Don't create them unnecessarily.)
if (o._hasExtensions && o._extensions._hasValues) {
return false;
}
} else {
if (!_extensions._equalValues(o._extensions)) return false;
}
if (_unknownFields == null || _unknownFields.isEmpty) {
// Check if other unknown fields is logically empty.
// (Don't create them unnecessarily.)
if (o._unknownFields != null && o._unknownFields.isNotEmpty) return false;
} else {
// Check if the other unknown fields has the same fields.
if (_unknownFields != o._unknownFields) return false;
}
return true;
}
bool _equalFieldValues(left, right) {
if (left != null && right != null) return _deepEquals(left, right);
var val = left ?? right;
// Two uninitialized fields are equal.
if (val == null) return true;
// One field is null. We are comparing an initialized field
// with its default value.
// An empty repeated field is the same as uninitialized.
// This is because accessing a repeated field automatically creates it.
// We don't want reading a field to change equality comparisons.
if (val is List && val.isEmpty) return true;
// For now, initialized and uninitialized fields are different.
// TODO(skybrian) consider other cases; should we compare with the
// default value or not?
return false;
}
/// Calculates a hash code based on the contents of the protobuf.
///
/// The hash may change when any field changes (recursively).
/// Therefore, protobufs used as map keys shouldn't be changed.
int get _hashCode {
int hash;
void hashEnumList(PbListBase enums) {
for (ProtobufEnum enm in enums) {
hash = 0x1fffffff & ((31 * hash) + enm.value);
}
}
// Hashes the value of one field (recursively).
void hashField(FieldInfo fi, value) {
if (value is List && value.isEmpty) {
return; // It's either repeated or an empty byte array.
}
hash = 0x1fffffff & ((37 * hash) + fi.tagNumber);
if (!_isEnum(fi.type)) {
hash = 0x1fffffff & ((53 * hash) + value.hashCode);
} else if (fi.isRepeated) {
hashEnumList(value);
} else {
ProtobufEnum enm = value;
hash = 0x1fffffff & ((53 * hash) + enm.value);
}
}
void hashEachField() {
for (var fi in _infosSortedByTag) {
var v = _values[fi.index];
if (v != null) hashField(fi, v);
}
if (!_hasExtensions) return;
for (int tagNumber in sorted(_extensions._tagNumbers)) {
var fi = _extensions._getInfoOrNull(tagNumber);
hashField(fi, _extensions._getFieldOrNull(fi));
}
}
// Generate hash.
hash = 41;
// Hash with descriptor.
hash = 0x1fffffff & ((19 * hash) + _meta.hashCode);
// Hash with fields.
hashEachField();
// Hash with unknown fields.
if (hasUnknownFields) {
hash = 0x1fffffff & ((29 * hash) + _unknownFields.hashCode);
}
return hash;
}
void writeString(StringBuffer out, String indent) {
void renderValue(key, value) {
if (value is GeneratedMessage) {
out.write('$indent$key: {\n');
value._fieldSet.writeString(out, '$indent ');
out.write('$indent}\n');
} else {
out.write('$indent$key: $value\n');
}
}
for (FieldInfo fi in _infosSortedByTag) {
var fieldValue = _values[fi.index];
if (fieldValue == null) continue;
if (fieldValue is ByteData) {
// TODO(skybrian): possibly unused. Delete?
final value = fieldValue.getUint64(0, Endian.little);
renderValue(fi.name, value);
} else if (fieldValue is List) {
for (var value in fieldValue) {
renderValue(fi.name, value);
}
} else {
renderValue(fi.name, fieldValue);
}
}
// TODO(skybrian) write extension fields? So far we haven't.
if (hasUnknownFields) {
out.write(_unknownFields.toString());
} else {
out.write(new UnknownFieldSet().toString());
}
}
/// Merges the contents of the [other] into this message.
///
/// Singular fields that are set in [other] overwrite the corresponding fields
/// in this message. Repeated fields are appended. Singular sub-messages are
/// recursively merged.
void _mergeFromMessage(_FieldSet other) {
// TODO(https://github.com/dart-lang/protobuf/issues/60): Recognize
// when [this] and [other] are the same protobuf (e.g. from cloning). In
// this case, we can merge the non-extension fields without field lookups or
// validation checks.
for (FieldInfo fi in other._infosSortedByTag) {
var value = other._values[fi.index];
if (value != null) _mergeField(fi, value);
}
if (other._hasExtensions) {
var others = other._extensions;
for (int tagNumber in others._tagNumbers) {
var extension = others._getInfoOrNull(tagNumber);
var value = others._getFieldOrNull(extension);
_mergeField(extension, value);
}
}
if (other.hasUnknownFields) {
_ensureUnknownFields().mergeFromUnknownFieldSet(other._unknownFields);
}
}
void _mergeField(FieldInfo otherFi, fieldValue) {
int tagNumber = otherFi.tagNumber;
// Determine the FieldInfo to use.
// Don't allow regular fields to be overwritten by extensions.
var fi = _nonExtensionInfo(tagNumber);
if (fi == null && otherFi is Extension) {
// This will overwrite any existing extension field info.
fi = otherFi;
}
bool mustClone = _isGroupOrMessage(otherFi.type);
if (fi.isRepeated) {
if (mustClone) {
// fieldValue must be a PbListBase of GeneratedMessage.
PbListBase<GeneratedMessage> pbList = fieldValue;
var repeatedFields = fi._ensureRepeatedField(this);
for (int i = 0; i < pbList.length; ++i) {
repeatedFields.add(_cloneMessage(pbList[i]));
}
} else {
// fieldValue must be at least a PbListBase.
PbListBase pbList = fieldValue;
fi._ensureRepeatedField(this).addAll(pbList);
}
return;
}
if (mustClone) {
fieldValue = _cloneMessage(fieldValue);
}
if (fi.index == null) {
_ensureExtensions()._setFieldAndInfo(fi, fieldValue);
} else {
_validateField(fi, fieldValue);
_setNonExtensionFieldUnchecked(fi, fieldValue);
}
}
// This function is declared as a static method rather than an in-place
// closure since dart2js does not currently hoist closures with no captured
// variables (See http://dartbug.com/26932), and dart2js will inline this
// version at the direct call site.
static GeneratedMessage _cloneMessage(GeneratedMessage message) =>
message.clone();
// Error-checking
/// Checks the value for a field that's about to be set.
void _validateField(FieldInfo fi, var newValue) {
_ensureWritable();
var message = _getFieldError(fi.type, newValue);
if (message != null) {
throw new ArgumentError(_setFieldFailedMessage(fi, newValue, message));
}
}
String _setFieldFailedMessage(FieldInfo fi, var value, String detail) {
return 'Illegal to set field ${fi.name} (${fi.tagNumber}) of $_messageName'
' to value ($value): $detail';
}
bool _hasRequiredValues() {
if (!_hasRequiredFields) return true;
for (var fi in _infos) {
var value = _values[fi.index];
if (!fi._hasRequiredValues(value)) return false;
}
return _hasRequiredExtensionValues();
}
bool _hasRequiredExtensionValues() {
if (!_hasExtensions) return true;
for (var fi in _extensions._infos) {
var value = _extensions._getFieldOrNull(fi);
if (!fi._hasRequiredValues(value)) return false;
}
return true; // No problems found.
}
/// Adds the path to each uninitialized field to the list.
void _appendInvalidFields(List<String> problems, String prefix) {
if (!_hasRequiredFields) return;
for (var fi in _infos) {
var value = _values[fi.index];
fi._appendInvalidFields(problems, value, prefix);
}
// TODO(skybrian): search extensions as well
// https://github.com/dart-lang/protobuf/issues/46
}
}