Commit 9e4f354f authored by Jon Skeet's avatar Jon Skeet

Prohibit null values in map fields

On deserialization, missing values for message types
are replaced with a "default" message.
parent 5700a105
...@@ -56,7 +56,7 @@ namespace Google.Protobuf.Collections ...@@ -56,7 +56,7 @@ namespace Google.Protobuf.Collections
} }
[Test] [Test]
public void NullValues() public void NullValuesProhibited()
{ {
TestNullValues<int?>(0); TestNullValues<int?>(0);
TestNullValues(""); TestNullValues("");
...@@ -65,19 +65,12 @@ namespace Google.Protobuf.Collections ...@@ -65,19 +65,12 @@ namespace Google.Protobuf.Collections
private void TestNullValues<T>(T nonNullValue) private void TestNullValues<T>(T nonNullValue)
{ {
var map = new MapField<int, T>(false); var map = new MapField<int, T>();
var nullValue = (T) (object) null; var nullValue = (T) (object) null;
Assert.Throws<ArgumentNullException>(() => map.Add(0, nullValue)); Assert.Throws<ArgumentNullException>(() => map.Add(0, nullValue));
Assert.Throws<ArgumentNullException>(() => map[0] = nullValue); Assert.Throws<ArgumentNullException>(() => map[0] = nullValue);
map.Add(1, nonNullValue); map.Add(1, nonNullValue);
map[1] = nonNullValue; map[1] = nonNullValue;
// Doesn't throw...
map = new MapField<int, T>(true);
map.Add(0, nullValue);
map[0] = nullValue;
map.Add(1, nonNullValue);
map[1] = nonNullValue;
} }
[Test] [Test]
...@@ -160,27 +153,6 @@ namespace Google.Protobuf.Collections ...@@ -160,27 +153,6 @@ namespace Google.Protobuf.Collections
EqualityTester.AssertInequality(map1, map2); EqualityTester.AssertInequality(map1, map2);
} }
[Test]
public void EqualityHandlesNullValues()
{
var map1 = new MapField<string, ForeignMessage>();
map1.Add("a", new ForeignMessage { C = 10 });
map1.Add("b", null);
var map2 = new MapField<string, ForeignMessage>();
map2.Add("a", new ForeignMessage { C = 10 });
map2.Add("b", null);
EqualityTester.AssertEquality(map1, map2);
// Check the null value isn't ignored entirely...
Assert.IsTrue(map1.Remove("b"));
EqualityTester.AssertInequality(map1, map2);
map1.Add("b", new ForeignMessage());
EqualityTester.AssertInequality(map1, map2);
map1["b"] = null;
EqualityTester.AssertEquality(map1, map2);
}
[Test] [Test]
public void Add_Dictionary() public void Add_Dictionary()
{ {
...@@ -453,30 +425,6 @@ namespace Google.Protobuf.Collections ...@@ -453,30 +425,6 @@ namespace Google.Protobuf.Collections
Assert.Throws<ArgumentNullException>(() => dictionary["x"] = null); Assert.Throws<ArgumentNullException>(() => dictionary["x"] = null);
} }
[Test]
public void AllowNullValues_Property()
{
// Non-message reference type values are non-nullable by default, but can be overridden
Assert.IsFalse(new MapField<int, string>().AllowsNullValues);
Assert.IsFalse(new MapField<int, string>(false).AllowsNullValues);
Assert.IsTrue(new MapField<int, string>(true).AllowsNullValues);
// Non-nullable value type values are never nullable
Assert.IsFalse(new MapField<int, int>().AllowsNullValues);
Assert.IsFalse(new MapField<int, int>(false).AllowsNullValues);
Assert.Throws<ArgumentException>(() => new MapField<int, int>(true));
// Message type values are nullable by default, but can be overridden
Assert.IsTrue(new MapField<int, TestAllTypes>().AllowsNullValues);
Assert.IsFalse(new MapField<int, TestAllTypes>(false).AllowsNullValues);
Assert.IsTrue(new MapField<int, TestAllTypes>(true).AllowsNullValues);
// Nullable value type values are nullable by default, but can be overridden
Assert.IsTrue(new MapField<int, int?>().AllowsNullValues);
Assert.IsFalse(new MapField<int, int?>(false).AllowsNullValues);
Assert.IsTrue(new MapField<int, int?>(true).AllowsNullValues);
}
[Test] [Test]
public void KeysReturnsLiveView() public void KeysReturnsLiveView()
{ {
......
...@@ -221,7 +221,7 @@ namespace Google.Protobuf ...@@ -221,7 +221,7 @@ namespace Google.Protobuf
}, },
MapInt32ForeignMessage = { MapInt32ForeignMessage = {
{ 0, new ForeignMessage { C = 10 } }, { 0, new ForeignMessage { C = 10 } },
{ 5, null }, { 5, new ForeignMessage() },
}, },
MapInt32Enum = { MapInt32Enum = {
{ 1, MapEnum.MAP_ENUM_BAR }, { 1, MapEnum.MAP_ENUM_BAR },
...@@ -268,6 +268,40 @@ namespace Google.Protobuf ...@@ -268,6 +268,40 @@ namespace Google.Protobuf
Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]); Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]);
} }
[Test]
public void MapWithOnlyKey_PrimitiveValue()
{
// Hand-craft the stream to contain a single entry with just a key.
var memoryStream = new MemoryStream();
var output = new CodedOutputStream(memoryStream);
output.WriteTag(TestMap.MapInt32DoubleFieldNumber, WireFormat.WireType.LengthDelimited);
int key = 10;
output.WriteLength(1 + CodedOutputStream.ComputeInt32Size(key));
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(0.0, parsed.MapInt32Double[key]);
}
[Test]
public void MapWithOnlyKey_MessageValue()
{
// Hand-craft the stream to contain a single entry with just a key.
var memoryStream = new MemoryStream();
var output = new CodedOutputStream(memoryStream);
output.WriteTag(TestMap.MapInt32ForeignMessageFieldNumber, WireFormat.WireType.LengthDelimited);
int key = 10;
output.WriteLength(1 + CodedOutputStream.ComputeInt32Size(key));
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(new ForeignMessage(), parsed.MapInt32ForeignMessage[key]);
}
[Test] [Test]
public void MapIgnoresExtraFieldsWithinEntryMessages() public void MapIgnoresExtraFieldsWithinEntryMessages()
{ {
......
...@@ -195,13 +195,6 @@ namespace Google.Protobuf ...@@ -195,13 +195,6 @@ namespace Google.Protobuf
AssertJson("{ 'repeatedForeignEnum': [ ] }", JsonFormatter.Default.Format(message)); AssertJson("{ 'repeatedForeignEnum': [ ] }", JsonFormatter.Default.Format(message));
} }
[Test]
public void NullValueForMessage()
{
var message = new TestMap { MapInt32ForeignMessage = { { 10, null } } };
AssertJson("{ 'mapInt32ForeignMessage': { '10': null } }", JsonFormatter.Default.Format(message));
}
[Test] [Test]
[TestCase("a\u17b4b", "a\\u17b4b")] // Explicit [TestCase("a\u17b4b", "a\\u17b4b")] // Explicit
[TestCase("a\u0601b", "a\\u0601b")] // Ranged [TestCase("a\u0601b", "a\\u0601b")] // Ranged
......
...@@ -2080,7 +2080,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2080,7 +2080,7 @@ namespace Google.Protobuf.TestProtos {
public const int DoubleFieldFieldNumber = 10; public const int DoubleFieldFieldNumber = 10;
private static readonly pbc::MapField<int, double?>.Codec _map_doubleField_codec private static readonly pbc::MapField<int, double?>.Codec _map_doubleField_codec
= new pbc::MapField<int, double?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<double>(18), 82); = new pbc::MapField<int, double?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<double>(18), 82);
private readonly pbc::MapField<int, double?> doubleField_ = new pbc::MapField<int, double?>(true); private readonly pbc::MapField<int, double?> doubleField_ = new pbc::MapField<int, double?>();
public pbc::MapField<int, double?> DoubleField { public pbc::MapField<int, double?> DoubleField {
get { return doubleField_; } get { return doubleField_; }
} }
...@@ -2089,7 +2089,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2089,7 +2089,7 @@ namespace Google.Protobuf.TestProtos {
public const int FloatFieldFieldNumber = 11; public const int FloatFieldFieldNumber = 11;
private static readonly pbc::MapField<int, float?>.Codec _map_floatField_codec private static readonly pbc::MapField<int, float?>.Codec _map_floatField_codec
= new pbc::MapField<int, float?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<float>(18), 90); = new pbc::MapField<int, float?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<float>(18), 90);
private readonly pbc::MapField<int, float?> floatField_ = new pbc::MapField<int, float?>(true); private readonly pbc::MapField<int, float?> floatField_ = new pbc::MapField<int, float?>();
public pbc::MapField<int, float?> FloatField { public pbc::MapField<int, float?> FloatField {
get { return floatField_; } get { return floatField_; }
} }
...@@ -2098,7 +2098,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2098,7 +2098,7 @@ namespace Google.Protobuf.TestProtos {
public const int Int64FieldFieldNumber = 12; public const int Int64FieldFieldNumber = 12;
private static readonly pbc::MapField<int, long?>.Codec _map_int64Field_codec private static readonly pbc::MapField<int, long?>.Codec _map_int64Field_codec
= new pbc::MapField<int, long?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<long>(18), 98); = new pbc::MapField<int, long?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<long>(18), 98);
private readonly pbc::MapField<int, long?> int64Field_ = new pbc::MapField<int, long?>(true); private readonly pbc::MapField<int, long?> int64Field_ = new pbc::MapField<int, long?>();
public pbc::MapField<int, long?> Int64Field { public pbc::MapField<int, long?> Int64Field {
get { return int64Field_; } get { return int64Field_; }
} }
...@@ -2107,7 +2107,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2107,7 +2107,7 @@ namespace Google.Protobuf.TestProtos {
public const int Uint64FieldFieldNumber = 13; public const int Uint64FieldFieldNumber = 13;
private static readonly pbc::MapField<int, ulong?>.Codec _map_uint64Field_codec private static readonly pbc::MapField<int, ulong?>.Codec _map_uint64Field_codec
= new pbc::MapField<int, ulong?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<ulong>(18), 106); = new pbc::MapField<int, ulong?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<ulong>(18), 106);
private readonly pbc::MapField<int, ulong?> uint64Field_ = new pbc::MapField<int, ulong?>(true); private readonly pbc::MapField<int, ulong?> uint64Field_ = new pbc::MapField<int, ulong?>();
public pbc::MapField<int, ulong?> Uint64Field { public pbc::MapField<int, ulong?> Uint64Field {
get { return uint64Field_; } get { return uint64Field_; }
} }
...@@ -2116,7 +2116,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2116,7 +2116,7 @@ namespace Google.Protobuf.TestProtos {
public const int Int32FieldFieldNumber = 14; public const int Int32FieldFieldNumber = 14;
private static readonly pbc::MapField<int, int?>.Codec _map_int32Field_codec private static readonly pbc::MapField<int, int?>.Codec _map_int32Field_codec
= new pbc::MapField<int, int?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<int>(18), 114); = new pbc::MapField<int, int?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<int>(18), 114);
private readonly pbc::MapField<int, int?> int32Field_ = new pbc::MapField<int, int?>(true); private readonly pbc::MapField<int, int?> int32Field_ = new pbc::MapField<int, int?>();
public pbc::MapField<int, int?> Int32Field { public pbc::MapField<int, int?> Int32Field {
get { return int32Field_; } get { return int32Field_; }
} }
...@@ -2125,7 +2125,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2125,7 +2125,7 @@ namespace Google.Protobuf.TestProtos {
public const int Uint32FieldFieldNumber = 15; public const int Uint32FieldFieldNumber = 15;
private static readonly pbc::MapField<int, uint?>.Codec _map_uint32Field_codec private static readonly pbc::MapField<int, uint?>.Codec _map_uint32Field_codec
= new pbc::MapField<int, uint?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<uint>(18), 122); = new pbc::MapField<int, uint?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<uint>(18), 122);
private readonly pbc::MapField<int, uint?> uint32Field_ = new pbc::MapField<int, uint?>(true); private readonly pbc::MapField<int, uint?> uint32Field_ = new pbc::MapField<int, uint?>();
public pbc::MapField<int, uint?> Uint32Field { public pbc::MapField<int, uint?> Uint32Field {
get { return uint32Field_; } get { return uint32Field_; }
} }
...@@ -2134,7 +2134,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2134,7 +2134,7 @@ namespace Google.Protobuf.TestProtos {
public const int BoolFieldFieldNumber = 16; public const int BoolFieldFieldNumber = 16;
private static readonly pbc::MapField<int, bool?>.Codec _map_boolField_codec private static readonly pbc::MapField<int, bool?>.Codec _map_boolField_codec
= new pbc::MapField<int, bool?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<bool>(18), 130); = new pbc::MapField<int, bool?>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForStructWrapper<bool>(18), 130);
private readonly pbc::MapField<int, bool?> boolField_ = new pbc::MapField<int, bool?>(true); private readonly pbc::MapField<int, bool?> boolField_ = new pbc::MapField<int, bool?>();
public pbc::MapField<int, bool?> BoolField { public pbc::MapField<int, bool?> BoolField {
get { return boolField_; } get { return boolField_; }
} }
...@@ -2143,7 +2143,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2143,7 +2143,7 @@ namespace Google.Protobuf.TestProtos {
public const int StringFieldFieldNumber = 17; public const int StringFieldFieldNumber = 17;
private static readonly pbc::MapField<int, string>.Codec _map_stringField_codec private static readonly pbc::MapField<int, string>.Codec _map_stringField_codec
= new pbc::MapField<int, string>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForClassWrapper<string>(18), 138); = new pbc::MapField<int, string>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForClassWrapper<string>(18), 138);
private readonly pbc::MapField<int, string> stringField_ = new pbc::MapField<int, string>(true); private readonly pbc::MapField<int, string> stringField_ = new pbc::MapField<int, string>();
public pbc::MapField<int, string> StringField { public pbc::MapField<int, string> StringField {
get { return stringField_; } get { return stringField_; }
} }
...@@ -2152,7 +2152,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -2152,7 +2152,7 @@ namespace Google.Protobuf.TestProtos {
public const int BytesFieldFieldNumber = 18; public const int BytesFieldFieldNumber = 18;
private static readonly pbc::MapField<int, pb::ByteString>.Codec _map_bytesField_codec private static readonly pbc::MapField<int, pb::ByteString>.Codec _map_bytesField_codec
= new pbc::MapField<int, pb::ByteString>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForClassWrapper<pb::ByteString>(18), 146); = new pbc::MapField<int, pb::ByteString>.Codec(pb::FieldCodec.ForInt32(8), pb::FieldCodec.ForClassWrapper<pb::ByteString>(18), 146);
private readonly pbc::MapField<int, pb::ByteString> bytesField_ = new pbc::MapField<int, pb::ByteString>(true); private readonly pbc::MapField<int, pb::ByteString> bytesField_ = new pbc::MapField<int, pb::ByteString>();
public pbc::MapField<int, pb::ByteString> BytesField { public pbc::MapField<int, pb::ByteString> BytesField {
get { return bytesField_; } get { return bytesField_; }
} }
......
...@@ -151,6 +151,8 @@ namespace Google.Protobuf.WellKnownTypes ...@@ -151,6 +151,8 @@ namespace Google.Protobuf.WellKnownTypes
[Test] [Test]
public void MapWrappersSerializeDeserialize() public void MapWrappersSerializeDeserialize()
{ {
// Note: no null values here, as they are prohibited in map fields
// (despite being representable).
var message = new MapWellKnownTypes var message = new MapWellKnownTypes
{ {
BoolField = { { 10, false }, { 20, true } }, BoolField = { { 10, false }, { 20, true } },
...@@ -158,13 +160,12 @@ namespace Google.Protobuf.WellKnownTypes ...@@ -158,13 +160,12 @@ namespace Google.Protobuf.WellKnownTypes
{ -1, ByteString.CopyFrom(1, 2, 3) }, { -1, ByteString.CopyFrom(1, 2, 3) },
{ 10, ByteString.CopyFrom(4, 5, 6) }, { 10, ByteString.CopyFrom(4, 5, 6) },
{ 1000, ByteString.Empty }, { 1000, ByteString.Empty },
{ 10000, null }
}, },
DoubleField = { { 1, 12.5 }, { 10, -1.5 }, { 20, 0d } }, DoubleField = { { 1, 12.5 }, { 10, -1.5 }, { 20, 0d } },
FloatField = { { 2, 123.25f }, { 3, -20f }, { 4, 0f } }, FloatField = { { 2, 123.25f }, { 3, -20f }, { 4, 0f } },
Int32Field = { { 5, int.MaxValue }, { 6, int.MinValue }, { 7, 0 } }, Int32Field = { { 5, int.MaxValue }, { 6, int.MinValue }, { 7, 0 } },
Int64Field = { { 8, long.MaxValue }, { 9, long.MinValue }, { 10, 0L } }, Int64Field = { { 8, long.MaxValue }, { 9, long.MinValue }, { 10, 0L } },
StringField = { { 11, "First" }, { 12, "Second" }, { 13, "" }, { 14, null } }, StringField = { { 11, "First" }, { 12, "Second" }, { 13, "" } },
Uint32Field = { { 15, uint.MaxValue }, { 16, uint.MinValue }, { 17, 0U } }, Uint32Field = { { 15, uint.MaxValue }, { 16, uint.MinValue }, { 17, 0U } },
Uint64Field = { { 18, ulong.MaxValue }, { 19, ulong.MinValue }, { 20, 0UL } }, Uint64Field = { { 18, ulong.MaxValue }, { 19, ulong.MinValue }, { 20, 0UL } },
}; };
...@@ -224,13 +225,11 @@ namespace Google.Protobuf.WellKnownTypes ...@@ -224,13 +225,11 @@ namespace Google.Protobuf.WellKnownTypes
[Test] [Test]
public void Reflection_MapFields() public void Reflection_MapFields()
{ {
// Just a single example... note that we can't have a null value here // Just a single example... note that we can't have a null value here despite the value type being int?
var message = new MapWellKnownTypes { Int32Field = { { 1, 2 }, { 3, null } } }; var message = new MapWellKnownTypes { Int32Field = { { 1, 2 } } };
var fields = MapWellKnownTypes.Descriptor.Fields; var fields = MapWellKnownTypes.Descriptor.Fields;
var dictionary = (IDictionary) fields[MapWellKnownTypes.Int32FieldFieldNumber].Accessor.GetValue(message); var dictionary = (IDictionary) fields[MapWellKnownTypes.Int32FieldFieldNumber].Accessor.GetValue(message);
Assert.AreEqual(2, dictionary[1]); Assert.AreEqual(2, dictionary[1]);
Assert.IsNull(dictionary[3]);
Assert.IsTrue(dictionary.Contains(3));
} }
[Test] [Test]
......
...@@ -53,6 +53,13 @@ namespace Google.Protobuf.Collections ...@@ -53,6 +53,13 @@ namespace Google.Protobuf.Collections
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />. /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
/// </para> /// </para>
/// <para> /// <para>
/// Null values are not permitted in the map, either for wrapper types or regular messages.
/// If a map is deserialized from a data stream and the value is missing from an entry, a default value
/// is created instead. For primitive types, that is the regular default value (0, the empty string and so
/// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
/// encoded value for the field.
/// </para>
/// <para>
/// This implementation does not generally prohibit the use of key/value types which are not /// This implementation does not generally prohibit the use of key/value types which are not
/// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee /// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
/// that all operations will work in such cases. /// that all operations will work in such cases.
...@@ -61,34 +68,10 @@ namespace Google.Protobuf.Collections ...@@ -61,34 +68,10 @@ namespace Google.Protobuf.Collections
public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
{ {
// TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
private readonly bool allowNullValues;
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(); new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>(); private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
/// <summary>
/// Constructs a new map field, defaulting the value nullability to only allow null values for message types
/// and non-nullable value types.
/// </summary>
public MapField() : this(typeof(IMessage).IsAssignableFrom(typeof(TValue)) || Nullable.GetUnderlyingType(typeof(TValue)) != null)
{
}
/// <summary>
/// Constructs a new map field, overriding the choice of whether null values are permitted in the map.
/// This is used by wrapper types, where maps with string and bytes wrappers as the value types
/// support null values.
/// </summary>
/// <param name="allowNullValues">Whether null values are permitted in the map or not.</param>
public MapField(bool allowNullValues)
{
if (allowNullValues && typeof(TValue).IsValueType() && Nullable.GetUnderlyingType(typeof(TValue)) == null)
{
throw new ArgumentException("allowNullValues", "Non-nullable value types do not support null values");
}
this.allowNullValues = allowNullValues;
}
/// <summary> /// <summary>
/// Creates a deep clone of this object. /// Creates a deep clone of this object.
/// </summary> /// </summary>
...@@ -97,13 +80,13 @@ namespace Google.Protobuf.Collections ...@@ -97,13 +80,13 @@ namespace Google.Protobuf.Collections
/// </returns> /// </returns>
public MapField<TKey, TValue> Clone() public MapField<TKey, TValue> Clone()
{ {
var clone = new MapField<TKey, TValue>(allowNullValues); var clone = new MapField<TKey, TValue>();
// Keys are never cloneable. Values might be. // Keys are never cloneable. Values might be.
if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
{ {
foreach (var pair in list) foreach (var pair in list)
{ {
clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable<TValue>)pair.Value).Clone()); clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
} }
} }
else else
...@@ -217,7 +200,7 @@ namespace Google.Protobuf.Collections ...@@ -217,7 +200,7 @@ namespace Google.Protobuf.Collections
{ {
Preconditions.CheckNotNullUnconstrained(key, "key"); Preconditions.CheckNotNullUnconstrained(key, "key");
// value == null check here is redundant, but avoids boxing. // value == null check here is redundant, but avoids boxing.
if (value == null && !allowNullValues) if (value == null)
{ {
Preconditions.CheckNotNullUnconstrained(value, "value"); Preconditions.CheckNotNullUnconstrained(value, "value");
} }
...@@ -246,7 +229,7 @@ namespace Google.Protobuf.Collections ...@@ -246,7 +229,7 @@ namespace Google.Protobuf.Collections
public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } } public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
/// <summary> /// <summary>
/// Adds the specified entries to the map. /// Adds the specified entries to the map. The keys and values are not automatically cloned.
/// </summary> /// </summary>
/// <param name="entries">The entries to add to the map.</param> /// <param name="entries">The entries to add to the map.</param>
public void Add(IDictionary<TKey, TValue> entries) public void Add(IDictionary<TKey, TValue> entries)
...@@ -346,11 +329,6 @@ namespace Google.Protobuf.Collections ...@@ -346,11 +329,6 @@ namespace Google.Protobuf.Collections
} }
} }
/// <summary>
/// Returns whether or not this map allows values to be null.
/// </summary>
public bool AllowsNullValues { get { return allowNullValues; } }
/// <summary> /// <summary>
/// Gets the number of elements contained in the map. /// Gets the number of elements contained in the map.
/// </summary> /// </summary>
...@@ -632,6 +610,8 @@ namespace Google.Protobuf.Collections ...@@ -632,6 +610,8 @@ namespace Google.Protobuf.Collections
/// </summary> /// </summary>
internal class MessageAdapter : IMessage internal class MessageAdapter : IMessage
{ {
private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
private readonly Codec codec; private readonly Codec codec;
internal TKey Key { get; set; } internal TKey Key { get; set; }
internal TValue Value { get; set; } internal TValue Value { get; set; }
...@@ -665,6 +645,13 @@ namespace Google.Protobuf.Collections ...@@ -665,6 +645,13 @@ namespace Google.Protobuf.Collections
input.SkipLastField(); input.SkipLastField();
} }
} }
// Corner case: a map entry with a key but no value, where the value type is a message.
// Read it as if we'd seen an input stream with no data (i.e. create a "default" message).
if (Value == null)
{
Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
}
} }
public void WriteTo(CodedOutputStream output) public void WriteTo(CodedOutputStream output)
......
...@@ -61,8 +61,8 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -61,8 +61,8 @@ namespace Google.Protobuf.WellKnownTypes {
/// ///
/// If the embedded message type is well-known and has a custom JSON /// If the embedded message type is well-known and has a custom JSON
/// representation, that representation will be embedded adding a field /// representation, that representation will be embedded adding a field
/// `value` which holds the custom JSON in addition to the the `@type` /// `value` which holds the custom JSON in addition to the `@type`
/// field. Example (for message [google.protobuf.Duration][google.protobuf.Duration]): /// field. Example (for message [google.protobuf.Duration][]):
/// ///
/// { /// {
/// "@type": "type.googleapis.com/google.protobuf.Duration", /// "@type": "type.googleapis.com/google.protobuf.Duration",
...@@ -110,7 +110,7 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -110,7 +110,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// * If no schema is provided, `https` is assumed. /// * If no schema is provided, `https` is assumed.
/// * The last segment of the URL's path must represent the fully /// * The last segment of the URL's path must represent the fully
/// qualified name of the type (as in `path/google.protobuf.Duration`). /// qualified name of the type (as in `path/google.protobuf.Duration`).
/// * An HTTP GET on the URL must yield a [google.protobuf.Type][google.protobuf.Type] /// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
/// value in binary format, or produce an error. /// value in binary format, or produce an error.
/// * Applications are allowed to cache lookup results based on the /// * Applications are allowed to cache lookup results based on the
/// URL, or have them precompiled into a binary to avoid any /// URL, or have them precompiled into a binary to avoid any
......
...@@ -653,7 +653,6 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -653,7 +653,6 @@ namespace Google.Protobuf.WellKnownTypes {
/// ///
/// package google.storage.v2; /// package google.storage.v2;
/// service Storage { /// service Storage {
/// // (-- see AccessControl.GetAcl --)
/// rpc GetAcl(GetAclRequest) returns (Acl); /// rpc GetAcl(GetAclRequest) returns (Acl);
/// ///
/// // Get a data record. /// // Get a data record.
......
...@@ -24,9 +24,9 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -24,9 +24,9 @@ namespace Google.Protobuf.WellKnownTypes {
byte[] descriptorData = global::System.Convert.FromBase64String( byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat( string.Concat(
"Chtnb29nbGUvcHJvdG9idWYvZW1wdHkucHJvdG8SD2dvb2dsZS5wcm90b2J1", "Chtnb29nbGUvcHJvdG9idWYvZW1wdHkucHJvdG8SD2dvb2dsZS5wcm90b2J1",
"ZiIHCgVFbXB0eUJNChNjb20uZ29vZ2xlLnByb3RvYnVmQgpFbXB0eVByb3Rv", "ZiIHCgVFbXB0eUJQChNjb20uZ29vZ2xlLnByb3RvYnVmQgpFbXB0eVByb3Rv",
"UAGgAQGiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNi", "UAGgAQH4AQGiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlw",
"BnByb3RvMw==")); "ZXNiBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { }, new pbr::FileDescriptor[] { },
new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] { new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
......
...@@ -69,7 +69,7 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -69,7 +69,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// z: 8 /// z: 8
/// ///
/// The result will not contain specific values for fields x,y and z /// The result will not contain specific values for fields x,y and z
/// (there value will be set to the default, and omitted in proto text /// (their value will be set to the default, and omitted in proto text
/// output): /// output):
/// ///
/// f { /// f {
......
...@@ -25,9 +25,9 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -25,9 +25,9 @@ namespace Google.Protobuf.WellKnownTypes {
string.Concat( string.Concat(
"Ch9nb29nbGUvcHJvdG9idWYvdGltZXN0YW1wLnByb3RvEg9nb29nbGUucHJv", "Ch9nb29nbGUvcHJvdG9idWYvdGltZXN0YW1wLnByb3RvEg9nb29nbGUucHJv",
"dG9idWYiKwoJVGltZXN0YW1wEg8KB3NlY29uZHMYASABKAMSDQoFbmFub3MY", "dG9idWYiKwoJVGltZXN0YW1wEg8KB3NlY29uZHMYASABKAMSDQoFbmFub3MY",
"AiABKAVCUQoTY29tLmdvb2dsZS5wcm90b2J1ZkIOVGltZXN0YW1wUHJvdG9Q", "AiABKAVCVAoTY29tLmdvb2dsZS5wcm90b2J1ZkIOVGltZXN0YW1wUHJvdG9Q",
"AaABAaICA0dQQqoCHkdvb2dsZS5Qcm90b2J1Zi5XZWxsS25vd25UeXBlc2IG", "AaABAfgBAaICA0dQQqoCHkdvb2dsZS5Qcm90b2J1Zi5XZWxsS25vd25UeXBl",
"cHJvdG8z")); "c2IGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { }, new pbr::FileDescriptor[] { },
new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] { new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
......
...@@ -29,9 +29,10 @@ namespace Google.Protobuf.WellKnownTypes { ...@@ -29,9 +29,10 @@ namespace Google.Protobuf.WellKnownTypes {
"KAMiHAoLVUludDY0VmFsdWUSDQoFdmFsdWUYASABKAQiGwoKSW50MzJWYWx1", "KAMiHAoLVUludDY0VmFsdWUSDQoFdmFsdWUYASABKAQiGwoKSW50MzJWYWx1",
"ZRINCgV2YWx1ZRgBIAEoBSIcCgtVSW50MzJWYWx1ZRINCgV2YWx1ZRgBIAEo", "ZRINCgV2YWx1ZRgBIAEoBSIcCgtVSW50MzJWYWx1ZRINCgV2YWx1ZRgBIAEo",
"DSIaCglCb29sVmFsdWUSDQoFdmFsdWUYASABKAgiHAoLU3RyaW5nVmFsdWUS", "DSIaCglCb29sVmFsdWUSDQoFdmFsdWUYASABKAgiHAoLU3RyaW5nVmFsdWUS",
"DQoFdmFsdWUYASABKAkiGwoKQnl0ZXNWYWx1ZRINCgV2YWx1ZRgBIAEoDEJQ", "DQoFdmFsdWUYASABKAkiGwoKQnl0ZXNWYWx1ZRINCgV2YWx1ZRgBIAEoDEJT",
"ChNjb20uZ29vZ2xlLnByb3RvYnVmQg1XcmFwcGVyc1Byb3RvUAGgAQGiAgNH", "ChNjb20uZ29vZ2xlLnByb3RvYnVmQg1XcmFwcGVyc1Byb3RvUAGgAQH4AQGi",
"UEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNiBnByb3RvMw==")); "AgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNiBnByb3Rv",
"Mw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { }, new pbr::FileDescriptor[] { },
new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] { new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
......
...@@ -62,7 +62,6 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -62,7 +62,6 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) {
descriptor_->message_type()->FindFieldByName("value"); descriptor_->message_type()->FindFieldByName("value");
variables_["key_type_name"] = type_name(key_descriptor); variables_["key_type_name"] = type_name(key_descriptor);
variables_["value_type_name"] = type_name(value_descriptor); variables_["value_type_name"] = type_name(value_descriptor);
variables_["true_for_wrappers"] = IsWrapperType(value_descriptor) ? "true" : "";
scoped_ptr<FieldGeneratorBase> key_generator(CreateFieldGenerator(key_descriptor, 1)); scoped_ptr<FieldGeneratorBase> key_generator(CreateFieldGenerator(key_descriptor, 1));
scoped_ptr<FieldGeneratorBase> value_generator(CreateFieldGenerator(value_descriptor, 2)); scoped_ptr<FieldGeneratorBase> value_generator(CreateFieldGenerator(value_descriptor, 2));
...@@ -76,7 +75,7 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -76,7 +75,7 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) {
printer->Print( printer->Print(
variables_, variables_,
", $tag$);\n" ", $tag$);\n"
"private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>($true_for_wrappers$);\n"); "private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>();\n");
WritePropertyDocComment(printer, descriptor_); WritePropertyDocComment(printer, descriptor_);
AddDeprecatedFlag(printer); AddDeprecatedFlag(printer);
printer->Print( printer->Print(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment