aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/ProtocolBuffers/FieldCodec.cs
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/FieldCodec.cs
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/FieldCodec.cs')
-rw-r--r--csharp/src/ProtocolBuffers/FieldCodec.cs149
1 files changed, 128 insertions, 21 deletions
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);
+ }
}
}