Override == and hashCode in PbMap (#224)
diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index 6b99ef8..26131fb 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.13.7
+
+* Override `operator ==` and `hashCode` in `PbMap` so that two `PbMap`s are equal if they have equal key/value pairs.
+
## 0.13.6
* Fixed equality check between messages with and without extensions.
diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart
index 13600cd..b7cd016 100644
--- a/protobuf/lib/src/protobuf/coded_buffer.dart
+++ b/protobuf/lib/src/protobuf/coded_buffer.dart
@@ -16,7 +16,7 @@
}
if (fs._hasExtensions) {
- for (var tagNumber in sorted(fs._extensions._tagNumbers)) {
+ for (var tagNumber in _sorted(fs._extensions._tagNumbers)) {
var fi = fs._extensions._getInfoOrNull(tagNumber);
out.writeField(tagNumber, fi.type, fs._extensions._getFieldOrNull(fi));
}
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 5252511..5f68124 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -523,51 +523,48 @@
/// 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) {
+ int hashField(int hash, FieldInfo fi, value) {
if (value is List && value.isEmpty) {
- return; // It's either repeated or an empty byte array.
+ return hash; // It's either repeated or an empty byte array.
}
- hash = 0x1fffffff & ((37 * hash) + fi.tagNumber);
+
+ hash = _HashUtils._combine(hash, fi.tagNumber);
if (!_isEnum(fi.type)) {
- hash = 0x1fffffff & ((53 * hash) + value.hashCode);
+ hash = _HashUtils._combine(hash, value.hashCode);
} else if (fi.isRepeated) {
- hashEnumList(value);
+ hash = _HashUtils._hashObjects(value.map((enm) => enm.value));
} else {
ProtobufEnum enm = value;
- hash = 0x1fffffff & ((53 * hash) + enm.value);
+ hash = _HashUtils._combine(hash, enm.value);
}
+
+ return hash;
}
- 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)) {
+ int hashEachField(int hash) {
+ //non-extension fields
+ hash = _infosSortedByTag.where((fi) => _values[fi.index] != null).fold(
+ hash, (int h, FieldInfo fi) => hashField(h, fi, _values[fi.index]));
+
+ if (!_hasExtensions) return hash;
+
+ hash =
+ _sorted(_extensions._tagNumbers).fold(hash, (int h, int tagNumber) {
var fi = _extensions._getInfoOrNull(tagNumber);
- hashField(fi, _extensions._getFieldOrNull(fi));
- }
+ return hashField(h, fi, _extensions._getFieldOrNull(fi));
+ });
+
+ return hash;
}
- // Generate hash.
- hash = 41;
// Hash with descriptor.
- hash = 0x1fffffff & ((19 * hash) + _meta.hashCode);
+ int hash = _HashUtils._combine(0, _meta.hashCode);
// Hash with fields.
- hashEachField();
+ hash = hashEachField(hash);
// Hash with unknown fields.
if (_hasUnknownFields) {
- hash = 0x1fffffff & ((29 * hash) + _unknownFields.hashCode);
+ hash = _HashUtils._combine(hash, _unknownFields.hashCode);
}
return hash;
}
diff --git a/protobuf/lib/src/protobuf/json.dart b/protobuf/lib/src/protobuf/json.dart
index cc716fd..f665608 100644
--- a/protobuf/lib/src/protobuf/json.dart
+++ b/protobuf/lib/src/protobuf/json.dart
@@ -62,7 +62,7 @@
result['${fi.tagNumber}'] = convertToMap(value, fi.type);
}
if (fs._hasExtensions) {
- for (int tagNumber in sorted(fs._extensions._tagNumbers)) {
+ for (int tagNumber in _sorted(fs._extensions._tagNumbers)) {
var value = fs._extensions._values[tagNumber];
if (value is List && value.isEmpty) {
continue; // It's repeated or an empty byte array.
diff --git a/protobuf/lib/src/protobuf/pb_list.dart b/protobuf/lib/src/protobuf/pb_list.dart
index 9b9f070..9282392 100644
--- a/protobuf/lib/src/protobuf/pb_list.dart
+++ b/protobuf/lib/src/protobuf/pb_list.dart
@@ -171,17 +171,8 @@
bool operator ==(other) =>
(other is PbListBase) && _areListsEqual(other, this);
- int get hashCode {
- int hash = 0;
- for (final value in _wrappedList) {
- hash = 0x1fffffff & (hash + value.hashCode);
- hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
- hash = hash ^ (hash >> 6);
- }
- hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
- hash = hash ^ (hash >> 11);
- return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
- }
+ @override
+ int get hashCode => _HashUtils._hashObjects(_wrappedList);
/// Returns an [Iterator] for the list.
Iterator<E> get iterator => _wrappedList.iterator;
diff --git a/protobuf/lib/src/protobuf/pb_map.dart b/protobuf/lib/src/protobuf/pb_map.dart
index 3d86db4..79a44f5 100644
--- a/protobuf/lib/src/protobuf/pb_map.dart
+++ b/protobuf/lib/src/protobuf/pb_map.dart
@@ -39,6 +39,40 @@
_wrappedMap[key] = value;
}
+ /// A [PbMap] is equal to another [PbMap] with equal key/value
+ /// pairs in any order.
+ @override
+ bool operator ==(other) {
+ if (identical(other, this)) {
+ return true;
+ }
+ if (other is! PbMap) {
+ return false;
+ }
+ if (other.length != length) {
+ return false;
+ }
+ for (final key in keys) {
+ if (!other.containsKey(key)) {
+ return false;
+ }
+ }
+ for (final key in keys) {
+ if (other[key] != this[key]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /// A [PbMap] is equal to another [PbMap] with equal key/value
+ /// pairs in any order. Then, the `hashCode` is guaranteed to be the same.
+ @override
+ int get hashCode {
+ return _wrappedMap.entries
+ .fold(0, (h, entry) => h ^ _HashUtils._hash2(entry.key, entry.value));
+ }
+
@override
void clear() {
if (_isReadonly)
diff --git a/protobuf/lib/src/protobuf/unknown_field_set.dart b/protobuf/lib/src/protobuf/unknown_field_set.dart
index 806f269..6bfffff 100644
--- a/protobuf/lib/src/protobuf/unknown_field_set.dart
+++ b/protobuf/lib/src/protobuf/unknown_field_set.dart
@@ -151,7 +151,7 @@
String _toString(String indent) {
var stringBuffer = StringBuffer();
- for (int tag in sorted(_fields.keys)) {
+ for (int tag in _sorted(_fields.keys)) {
var field = _fields[tag];
for (var value in field.values) {
if (value is UnknownFieldSet) {
diff --git a/protobuf/lib/src/protobuf/utils.dart b/protobuf/lib/src/protobuf/utils.dart
index efd8c4c..2692d5d 100644
--- a/protobuf/lib/src/protobuf/utils.dart
+++ b/protobuf/lib/src/protobuf/utils.dart
@@ -35,4 +35,32 @@
return _areListsEqual(asBytes(lhs), asBytes(rhs));
}
-List<T> sorted<T>(Iterable<T> list) => List.from(list)..sort();
+@Deprecated("This function was not intended to be public. "
+ "It will be removed from the public api in next major version. ")
+List<T> sorted<T>(Iterable<T> list) => new List.from(list)..sort();
+
+List<T> _sorted<T>(Iterable<T> list) => new List.from(list)..sort();
+
+class _HashUtils {
+// Jenkins hash functions copied from https://github.com/google/quiver-dart/blob/master/lib/src/core/hash.dart.
+
+ static int _combine(int hash, int value) {
+ hash = 0x1fffffff & (hash + value);
+ hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+ return hash ^ (hash >> 6);
+ }
+
+ static int _finish(int hash) {
+ hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+ hash = hash ^ (hash >> 11);
+ return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+ }
+
+ /// Generates a hash code for multiple [objects].
+ static int _hashObjects(Iterable objects) =>
+ _finish(objects.fold(0, (h, i) => _combine(h, i.hashCode)));
+
+ /// Generates a hash code for two objects.
+ static int _hash2(a, b) =>
+ _finish(_combine(_combine(0, a.hashCode), b.hashCode));
+}
diff --git a/protobuf/pubspec.yaml b/protobuf/pubspec.yaml
index 5e20e5c..63f0a13 100644
--- a/protobuf/pubspec.yaml
+++ b/protobuf/pubspec.yaml
@@ -1,5 +1,5 @@
name: protobuf
-version: 0.13.6
+version: 0.13.7
author: Dart Team <misc@dartlang.org>
description: >
Runtime library for protocol buffers support.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index 6c854fb..3d09da9 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,4 +1,5 @@
## 16.0.3
+
* Sync Kythe metadata updates from internal repo:
* Remove the extra 1 (field name) from generated field paths,
so they refer to the whole field rather than the name.
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index 0e26799..78e9f0f 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
name: protoc_plugin
-version: 16.0.3-dev-1
+version: 16.0.3-dev-2
author: Dart Team <misc@dartlang.org>
description: Protoc compiler plugin to generate Dart code
homepage: https://github.com/dart-lang/protobuf
@@ -10,7 +10,7 @@
dependencies:
fixnum: ^0.10.5
path: ^1.0.0
- protobuf: ^0.13.6
+ protobuf: ^0.13.7
dart_style: ^1.0.6
dev_dependencies:
diff --git a/protoc_plugin/test/map_field_test.dart b/protoc_plugin/test/map_field_test.dart
index 922689a..7c5c35d 100644
--- a/protoc_plugin/test/map_field_test.dart
+++ b/protoc_plugin/test/map_field_test.dart
@@ -7,6 +7,7 @@
import 'dart:convert';
+import 'package:protobuf/protobuf.dart';
import 'package:test/test.dart';
import '../out/protos/map_field.pb.dart';
@@ -212,6 +213,33 @@
_expectEmpty(testMap);
});
+ test(
+ 'PbMap` is equal to another PbMap with equal key/value pairs in any order',
+ () {
+ TestMap t = TestMap()
+ ..int32ToStringField[2] = 'test2'
+ ..int32ToStringField[1] = 'test';
+ TestMap t2 = TestMap()
+ ..int32ToStringField[1] = 'test'
+ ..int32ToStringField[2] = 'test2';
+ TestMap t3 = TestMap()..int32ToStringField[1] = 'test';
+
+ PbMap<int, String> m = t.int32ToStringField;
+ PbMap<int, String> m2 = t2.int32ToStringField;
+ PbMap<int, String> m3 = t3.int32ToStringField;
+
+ expect(t, t2);
+ expect(t.hashCode, t2.hashCode);
+
+ expect(m, m2);
+ expect(m == m2, isTrue);
+ expect(m.hashCode, m2.hashCode);
+
+ expect(m, isNot(m3));
+ expect(m == m3, isFalse);
+ expect(m.hashCode, isNot(m3.hashCode));
+ });
+
test('merge from other message', () {
TestMap testMap = TestMap();
_setValues(testMap);