aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/ProtocolBuffers
diff options
context:
space:
mode:
authorJon Skeet <jonskeet@google.com>2015-07-16 17:03:06 +0100
committerJon Skeet <jonskeet@google.com>2015-07-16 17:03:06 +0100
commit8a0312b20156aa4df092cb6b3c665ef48cb6fa54 (patch)
tree8a5864fa5e8481ed8b04a25404cf51bc1818a37e /csharp/src/ProtocolBuffers
parent8d47ec4f3e3368c5f4e7ac195f20978abc8a692f (diff)
downloadprotobuf-8a0312b20156aa4df092cb6b3c665ef48cb6fa54.tar.gz
protobuf-8a0312b20156aa4df092cb6b3c665ef48cb6fa54.tar.bz2
protobuf-8a0312b20156aa4df092cb6b3c665ef48cb6fa54.zip
First pass at wrapper types.
- We do still generate the message types, as otherwise reflection breaks, even though it doesn't actually use those types. - JSON handling hasn't been implemented yet
Diffstat (limited to 'csharp/src/ProtocolBuffers')
-rw-r--r--csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs8
-rw-r--r--csharp/src/ProtocolBuffers/CodedOutputStream.cs4
-rw-r--r--csharp/src/ProtocolBuffers/Collections/MapField.cs34
-rw-r--r--csharp/src/ProtocolBuffers/Collections/RepeatedField.cs2
-rw-r--r--csharp/src/ProtocolBuffers/FieldCodec.cs149
5 files changed, 166 insertions, 31 deletions
diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs
index a3e7c1bb..bf221c9c 100644
--- a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs
+++ b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs
@@ -129,8 +129,7 @@ namespace Google.Protobuf
public static int ComputeStringSize(String value)
{
int byteArraySize = Utf8Encoding.GetByteCount(value);
- return ComputeRawVarint32Size((uint) byteArraySize) +
- byteArraySize;
+ return ComputeLengthSize(byteArraySize) + byteArraySize;
}
/// <summary>
@@ -149,7 +148,7 @@ namespace Google.Protobuf
public static int ComputeMessageSize(IMessage value)
{
int size = value.CalculateSize();
- return ComputeRawVarint32Size((uint) size) + size;
+ return ComputeLengthSize(size) + size;
}
/// <summary>
@@ -158,8 +157,7 @@ namespace Google.Protobuf
/// </summary>
public static int ComputeBytesSize(ByteString value)
{
- return ComputeRawVarint32Size((uint) value.Length) +
- value.Length;
+ return ComputeLengthSize(value.Length) + value.Length;
}
/// <summary>
diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.cs
index 53f04c77..b91d6d70 100644
--- a/csharp/src/ProtocolBuffers/CodedOutputStream.cs
+++ b/csharp/src/ProtocolBuffers/CodedOutputStream.cs
@@ -274,7 +274,7 @@ namespace Google.Protobuf
/// <param name="value">The value to write</param>
public void WriteMessage(IMessage value)
{
- WriteRawVarint32((uint) value.CalculateSize());
+ WriteLength(value.CalculateSize());
value.WriteTo(this);
}
@@ -285,7 +285,7 @@ namespace Google.Protobuf
/// <param name="value">The value to write</param>
public void WriteBytes(ByteString value)
{
- WriteRawVarint32((uint) value.Length);
+ WriteLength(value.Length);
value.WriteRawBytesTo(this);
}
diff --git a/csharp/src/ProtocolBuffers/Collections/MapField.cs b/csharp/src/ProtocolBuffers/Collections/MapField.cs
index 0f379eaa..ead45737 100644
--- a/csharp/src/ProtocolBuffers/Collections/MapField.cs
+++ b/csharp/src/ProtocolBuffers/Collections/MapField.cs
@@ -51,14 +51,38 @@ namespace Google.Protobuf.Collections
public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IFreezable, 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.)
+ private bool allowNullValues;
private bool frozen;
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
new Dictionary<TKey, LinkedListNode<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;
+ }
+
public MapField<TKey, TValue> Clone()
{
- var clone = new MapField<TKey, TValue>();
+ var clone = new MapField<TKey, TValue>(allowNullValues);
// Keys are never cloneable. Values might be.
if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
{
@@ -138,7 +162,8 @@ namespace Google.Protobuf.Collections
set
{
ThrowHelper.ThrowIfNull(key, "key");
- if (value == null && (typeof(TValue) == typeof(ByteString) || typeof(TValue) == typeof(string)))
+ // value == null check here is redundant, but avoids boxing.
+ if (value == null && !allowNullValues)
{
ThrowHelper.ThrowIfNull(value, "value");
}
@@ -225,6 +250,11 @@ namespace Google.Protobuf.Collections
}
}
+ /// <summary>
+ /// Returns whether or not this map allows values to be null.
+ /// </summary>
+ public bool AllowsNullValues { get { return allowNullValues; } }
+
public int Count { get { return list.Count; } }
public bool IsReadOnly { get { return frozen; } }
diff --git a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs
index 8375ae0b..9bab41ea 100644
--- a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs
+++ b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs
@@ -123,7 +123,7 @@ namespace Google.Protobuf.Collections
{
int dataSize = CalculatePackedDataSize(codec);
return CodedOutputStream.ComputeRawVarint32Size(tag) +
- CodedOutputStream.ComputeRawVarint32Size((uint)dataSize) +
+ CodedOutputStream.ComputeLengthSize(dataSize) +
dataSize;
}
else
diff --git a/csharp/src/ProtocolBuffers/FieldCodec.cs b/csharp/src/ProtocolBuffers/FieldCodec.cs
index c72a3e7b..caf03286 100644
--- a/csharp/src/ProtocolBuffers/FieldCodec.cs
+++ b/csharp/src/ProtocolBuffers/FieldCodec.cs
@@ -29,7 +29,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
-
+
using System;
using System.Collections.Generic;
@@ -43,7 +43,7 @@ namespace Google.Protobuf
// TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
public static FieldCodec<string> ForString(uint tag)
{
- return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
+ return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
}
public static FieldCodec<ByteString> ForBytes(uint tag)
@@ -131,6 +131,106 @@ namespace Google.Protobuf
return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
(output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
}
+
+ /// <summary>
+ /// Creates a codec for a wrapper type of a class - which must be string or ByteString.
+ /// </summary>
+ public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class
+ {
+ var nestedCodec = WrapperCodecs.GetCodec<T>();
+ return new FieldCodec<T>(
+ input => WrapperCodecs.Read<T>(input, nestedCodec),
+ (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
+ value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
+ tag,
+ null); // Default value for the wrapper
+ }
+
+ /// <summary>
+ /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64,
+ /// Bool, Single or Double.
+ /// </summary>
+ public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct
+ {
+ var nestedCodec = WrapperCodecs.GetCodec<T>();
+ return new FieldCodec<T?>(
+ input => WrapperCodecs.Read<T>(input, nestedCodec),
+ (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
+ value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
+ tag,
+ null); // Default value for the wrapper
+ }
+
+ // Helper code to create codecs for wrapper types. Somewhat ugly with all the
+ private static class WrapperCodecs
+ {
+ private static readonly Dictionary<Type, object> Codecs = new Dictionary<Type, object>
+ {
+ { typeof(bool), ForBool(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+ { typeof(int), ForInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+ { typeof(long), ForInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+ { typeof(uint), ForUInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+ { typeof(ulong), ForUInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+ { typeof(float), ForFloat(WireFormat.MakeTag(1, WireFormat.WireType.Fixed32)) },
+ { typeof(double), ForDouble(WireFormat.MakeTag(1, WireFormat.WireType.Fixed64)) },
+ { typeof(string), ForString(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) },
+ { typeof(ByteString), ForBytes(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) }
+ };
+
+ /// <summary>
+ /// Returns a field codec which effectively wraps a value of type T in a message.
+ ///
+ /// </summary>
+ internal static FieldCodec<T> GetCodec<T>()
+ {
+ object value;
+ if (!Codecs.TryGetValue(typeof(T), out value))
+ {
+ throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
+ }
+ return (FieldCodec<T>) value;
+ }
+
+ internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec)
+ {
+ int length = input.ReadLength();
+ int oldLimit = input.PushLimit(length);
+
+ uint tag;
+ T value = codec.DefaultValue;
+ while (input.ReadTag(out tag))
+ {
+ if (tag == 0)
+ {
+ throw InvalidProtocolBufferException.InvalidTag();
+ }
+ if (tag == codec.Tag)
+ {
+ value = codec.Read(input);
+ }
+ if (WireFormat.IsEndGroupTag(tag))
+ {
+ break;
+ }
+ }
+ input.CheckLastTagWas(0);
+ input.PopLimit(oldLimit);
+
+ return value;
+ }
+
+ internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
+ {
+ output.WriteLength(codec.CalculateSizeWithTag(value));
+ codec.WriteTagAndValue(output, value);
+ }
+
+ internal static int CalculateSize<T>(T value, FieldCodec<T> codec)
+ {
+ int fieldLength = codec.CalculateSizeWithTag(value);
+ return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
+ }
+ }
}
/// <summary>
@@ -144,31 +244,19 @@ namespace Google.Protobuf
/// </remarks>
public sealed class FieldCodec<T>
{
- private static readonly Func<T, bool> IsDefault;
- private static readonly T Default;
+ private static readonly T DefaultDefault;
static FieldCodec()
{
if (typeof(T) == typeof(string))
{
- Default = (T)(object)"";
- IsDefault = CreateDefaultValueCheck<string>(x => x.Length == 0);
+ DefaultDefault = (T)(object)"";
}
else if (typeof(T) == typeof(ByteString))
{
- Default = (T)(object)ByteString.Empty;
- IsDefault = CreateDefaultValueCheck<ByteString>(x => x.Length == 0);
- }
- else if (!typeof(T).IsValueType)
- {
- // Default default
- IsDefault = CreateDefaultValueCheck<T>(x => x == null);
- }
- else
- {
- // Default default
- IsDefault = CreateDefaultValueCheck<T>(x => EqualityComparer<T>.Default.Equals(x, default(T)));
+ DefaultDefault = (T)(object)ByteString.Empty;
}
+ // Otherwise it's the default value of the CLR type
}
private static Func<T, bool> CreateDefaultValueCheck<TTmp>(Func<TTmp, bool> check)
@@ -182,18 +270,32 @@ namespace Google.Protobuf
private readonly uint tag;
private readonly int tagSize;
private readonly int fixedSize;
+ // Default value for this codec. Usually the same for every instance of the same type, but
+ // for string/ByteString wrapper fields the codec's default value is null, whereas for
+ // other string/ByteString fields it's "" or ByteString.Empty.
+ private readonly T defaultValue;
internal FieldCodec(
Func<CodedInputStream, T> reader,
Action<CodedOutputStream, T> writer,
Func<T, int> sizeCalculator,
- uint tag)
+ uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault)
+ {
+ }
+
+ internal FieldCodec(
+ Func<CodedInputStream, T> reader,
+ Action<CodedOutputStream, T> writer,
+ Func<T, int> sizeCalculator,
+ uint tag,
+ T defaultValue)
{
this.reader = reader;
this.writer = writer;
this.sizeCalculator = sizeCalculator;
this.fixedSize = 0;
this.tag = tag;
+ this.defaultValue = defaultValue;
tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
}
@@ -234,7 +336,7 @@ namespace Google.Protobuf
public uint Tag { get { return tag; } }
- public T DefaultValue { get { return Default; } }
+ public T DefaultValue { get { return defaultValue; } }
/// <summary>
/// Write a tag and the given value, *if* the value is not the default.
@@ -260,6 +362,11 @@ namespace Google.Protobuf
public int CalculateSizeWithTag(T value)
{
return IsDefault(value) ? 0 : sizeCalculator(value) + tagSize;
- }
+ }
+
+ private bool IsDefault(T value)
+ {
+ return EqualityComparer<T>.Default.Equals(value, defaultValue);
+ }
}
}