From 8a0312b20156aa4df092cb6b3c665ef48cb6fa54 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 16 Jul 2015 17:03:06 +0100 Subject: 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 --- csharp/src/ProtocolBuffers/FieldCodec.cs | 149 ++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 21 deletions(-) (limited to 'csharp/src/ProtocolBuffers/FieldCodec.cs') 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 ForString(uint tag) { - return new FieldCodec(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); + return new FieldCodec(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); } public static FieldCodec ForBytes(uint tag) @@ -131,6 +131,106 @@ namespace Google.Protobuf return new FieldCodec(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; }, (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag); } + + /// + /// Creates a codec for a wrapper type of a class - which must be string or ByteString. + /// + public static FieldCodec ForClassWrapper(uint tag) where T : class + { + var nestedCodec = WrapperCodecs.GetCodec(); + return new FieldCodec( + input => WrapperCodecs.Read(input, nestedCodec), + (output, value) => WrapperCodecs.Write(output, value, nestedCodec), + value => WrapperCodecs.CalculateSize(value, nestedCodec), + tag, + null); // Default value for the wrapper + } + + /// + /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64, + /// Bool, Single or Double. + /// + public static FieldCodec ForStructWrapper(uint tag) where T : struct + { + var nestedCodec = WrapperCodecs.GetCodec(); + return new FieldCodec( + input => WrapperCodecs.Read(input, nestedCodec), + (output, value) => WrapperCodecs.Write(output, value.Value, nestedCodec), + value => value == null ? 0 : WrapperCodecs.CalculateSize(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 Codecs = new Dictionary + { + { 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)) } + }; + + /// + /// Returns a field codec which effectively wraps a value of type T in a message. + /// + /// + internal static FieldCodec GetCodec() + { + object value; + if (!Codecs.TryGetValue(typeof(T), out value)) + { + throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T)); + } + return (FieldCodec) value; + } + + internal static T Read(CodedInputStream input, FieldCodec 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(CodedOutputStream output, T value, FieldCodec codec) + { + output.WriteLength(codec.CalculateSizeWithTag(value)); + codec.WriteTagAndValue(output, value); + } + + internal static int CalculateSize(T value, FieldCodec codec) + { + int fieldLength = codec.CalculateSizeWithTag(value); + return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength; + } + } } /// @@ -144,31 +244,19 @@ namespace Google.Protobuf /// public sealed class FieldCodec { - private static readonly Func IsDefault; - private static readonly T Default; + private static readonly T DefaultDefault; static FieldCodec() { if (typeof(T) == typeof(string)) { - Default = (T)(object)""; - IsDefault = CreateDefaultValueCheck(x => x.Length == 0); + DefaultDefault = (T)(object)""; } else if (typeof(T) == typeof(ByteString)) { - Default = (T)(object)ByteString.Empty; - IsDefault = CreateDefaultValueCheck(x => x.Length == 0); - } - else if (!typeof(T).IsValueType) - { - // Default default - IsDefault = CreateDefaultValueCheck(x => x == null); - } - else - { - // Default default - IsDefault = CreateDefaultValueCheck(x => EqualityComparer.Default.Equals(x, default(T))); + DefaultDefault = (T)(object)ByteString.Empty; } + // Otherwise it's the default value of the CLR type } private static Func CreateDefaultValueCheck(Func 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 reader, Action writer, Func sizeCalculator, - uint tag) + uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault) + { + } + + internal FieldCodec( + Func reader, + Action writer, + Func 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; } } /// /// 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.Default.Equals(value, defaultValue); + } } } -- cgit v1.2.3