FieldCodec.cs 20.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (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
32

33
using Google.Protobuf.Compatibility;
34
using Google.Protobuf.WellKnownTypes;
35
using System;
Jon Skeet's avatar
Jon Skeet committed
36 37 38 39 40 41 42 43 44
using System.Collections.Generic;

namespace Google.Protobuf
{
    /// <summary>
    /// Factory methods for <see cref="FieldCodec{T}"/>.
    /// </summary>
    public static class FieldCodec
    {
45
        // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
46 47 48 49 50 51

        /// <summary>
        /// Retrieves a codec suitable for a string field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
52 53
        public static FieldCodec<string> ForString(uint tag)
        {
54
            return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
Jon Skeet's avatar
Jon Skeet committed
55 56
        }

57 58 59 60 61
        /// <summary>
        /// Retrieves a codec suitable for a bytes field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
62 63 64 65 66
        public static FieldCodec<ByteString> ForBytes(uint tag)
        {
            return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag);
        }

67 68 69 70 71
        /// <summary>
        /// Retrieves a codec suitable for a bool field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
72 73 74 75 76
        public static FieldCodec<bool> ForBool(uint tag)
        {
            return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag);
        }

77 78 79 80 81
        /// <summary>
        /// Retrieves a codec suitable for an int32 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
82 83 84 85 86
        public static FieldCodec<int> ForInt32(uint tag)
        {
            return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag);
        }

87 88 89 90 91
        /// <summary>
        /// Retrieves a codec suitable for an sint32 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
92 93 94 95 96
        public static FieldCodec<int> ForSInt32(uint tag)
        {
            return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag);
        }

97 98 99 100 101
        /// <summary>
        /// Retrieves a codec suitable for a fixed32 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
102
        public static FieldCodec<uint> ForFixed32(uint tag)
Jon Skeet's avatar
Jon Skeet committed
103
        {
104
            return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag);
Jon Skeet's avatar
Jon Skeet committed
105 106
        }

107 108 109 110 111
        /// <summary>
        /// Retrieves a codec suitable for an sfixed32 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
112 113
        public static FieldCodec<int> ForSFixed32(uint tag)
        {
114
            return new FieldCodec<int>(input => input.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag);
115 116
        }

117 118 119 120 121
        /// <summary>
        /// Retrieves a codec suitable for a uint32 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
122 123 124 125 126
        public static FieldCodec<uint> ForUInt32(uint tag)
        {
            return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag);
        }

127 128 129 130 131
        /// <summary>
        /// Retrieves a codec suitable for an int64 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
132 133 134 135 136
        public static FieldCodec<long> ForInt64(uint tag)
        {
            return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag);
        }

137 138 139 140 141
        /// <summary>
        /// Retrieves a codec suitable for an sint64 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
142 143 144 145 146
        public static FieldCodec<long> ForSInt64(uint tag)
        {
            return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag);
        }

147 148 149 150 151
        /// <summary>
        /// Retrieves a codec suitable for a fixed64 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
152
        public static FieldCodec<ulong> ForFixed64(uint tag)
Jon Skeet's avatar
Jon Skeet committed
153
        {
154
            return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag);
Jon Skeet's avatar
Jon Skeet committed
155 156
        }

157 158 159 160 161
        /// <summary>
        /// Retrieves a codec suitable for an sfixed64 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
162 163
        public static FieldCodec<long> ForSFixed64(uint tag)
        {
164
            return new FieldCodec<long>(input => input.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag);
165 166
        }

167 168 169 170 171
        /// <summary>
        /// Retrieves a codec suitable for a uint64 field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
172 173 174 175 176
        public static FieldCodec<ulong> ForUInt64(uint tag)
        {
            return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag);
        }

177 178 179 180 181
        /// <summary>
        /// Retrieves a codec suitable for a float field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
182 183 184 185 186
        public static FieldCodec<float> ForFloat(uint tag)
        {
            return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag);
        }

187 188 189 190 191
        /// <summary>
        /// Retrieves a codec suitable for a double field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
192 193
        public static FieldCodec<double> ForDouble(uint tag)
        {
194
            return new FieldCodec<double>(input => input.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag);
Jon Skeet's avatar
Jon Skeet committed
195 196 197
        }

        // Enums are tricky. We can probably use expression trees to build these delegates automatically,
198
        // but it's easy to generate the code for it.
199 200 201 202 203 204 205 206

        /// <summary>
        /// Retrieves a codec suitable for an enum field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
        /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
207 208 209 210 211 212 213 214
        public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
        {
            return new FieldCodec<T>(input => fromInt32(
                input.ReadEnum()),
                (output, value) => output.WriteEnum(toInt32(value)),
                value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag);
        }

215 216 217 218 219 220
        /// <summary>
        /// Retrieves a codec suitable for a message field with the given tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="parser">A parser to use for the message type.</param>
        /// <returns>A codec for the given tag.</returns>
Jon Skeet's avatar
Jon Skeet committed
221 222 223 224 225
        public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : IMessage<T>
        {
            return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
                (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
        }
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

        /// <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
        }

Jon Skeet's avatar
Jon Skeet committed
256 257 258 259 260 261 262 263
        /// <summary>
        /// Helper code to create codecs for wrapper types.
        /// </summary>
        /// <remarks>
        /// Somewhat ugly with all the static methods, but the conversions involved to/from nullable types make it
        /// slightly tricky to improve. So long as we keep the public API (ForClassWrapper, ForStructWrapper) in place,
        /// we can refactor later if we come up with something cleaner.
        /// </remarks>
264 265
        private static class WrapperCodecs
        {
266
            private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object>
267
            {
268 269 270 271 272 273 274 275 276
                { typeof(bool), ForBool(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
                { typeof(int), ForInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
                { typeof(long), ForInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
                { typeof(uint), ForUInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
                { typeof(ulong), ForUInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
                { typeof(float), ForFloat(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed32)) },
                { typeof(double), ForDouble(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed64)) },
                { typeof(string), ForString(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) },
                { typeof(ByteString), ForBytes(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) }
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
            };

            /// <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;
300
                while ((tag = input.ReadTag()) != 0)
301 302 303 304 305
                {
                    if (tag == codec.Tag)
                    {
                        value = codec.Read(input);
                    }
Jon Skeet's avatar
Jon Skeet committed
306
                    else
307
                    {
Jon Skeet's avatar
Jon Skeet committed
308
                        input.SkipLastField();
309
                    }
Jon Skeet's avatar
Jon Skeet committed
310

311
                }
Jon Skeet's avatar
Jon Skeet committed
312
                input.CheckReadEndOfStreamTag();
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
                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;
            }
        }
Jon Skeet's avatar
Jon Skeet committed
330 331 332
    }

    /// <summary>
333
    /// <para>
Jon Skeet's avatar
Jon Skeet committed
334 335 336
    /// An encode/decode pair for a single field. This effectively encapsulates
    /// all the information needed to read or write the field value from/to a coded
    /// stream.
337 338 339 340 341
    /// </para>
    /// <para>
    /// This class is public and has to be as it is used by generated code, but its public
    /// API is very limited - just what the generated code needs to call directly.
    /// </para>
Jon Skeet's avatar
Jon Skeet committed
342 343
    /// </summary>
    /// <remarks>
344 345
    /// This never writes default values to the stream, and does not address "packedness"
    /// in repeated fields itself, other than to know whether or not the field *should* be packed.
Jon Skeet's avatar
Jon Skeet committed
346 347 348
    /// </remarks>
    public sealed class FieldCodec<T>
    {
349
        private static readonly T DefaultDefault;
350 351
        // Only non-nullable value types support packing. This is the simplest way of detecting that.
        private static readonly bool TypeSupportsPacking = default(T) != null;
Jon Skeet's avatar
Jon Skeet committed
352 353 354 355 356

        static FieldCodec()
        {
            if (typeof(T) == typeof(string))
            {
357
                DefaultDefault = (T)(object)"";
Jon Skeet's avatar
Jon Skeet committed
358 359 360
            }
            else if (typeof(T) == typeof(ByteString))
            {
361
                DefaultDefault = (T)(object)ByteString.Empty;
Jon Skeet's avatar
Jon Skeet committed
362
            }
363
            // Otherwise it's the default value of the CLR type
Jon Skeet's avatar
Jon Skeet committed
364 365
        }

366 367
        internal static bool IsPackedRepeatedField(uint tag) =>
            TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited;
Jon Skeet's avatar
Jon Skeet committed
368

369
        internal bool PackedRepeatedField { get; }
370 371

        /// <summary>
372
        /// Returns a delegate to write a value (unconditionally) to a coded output stream.
373
        /// </summary>
374
        internal Action<CodedOutputStream, T> ValueWriter { get; }
375 376

        /// <summary>
377
        /// Returns the size calculator for just a value.
378
        /// </summary>
379
        internal Func<T, int> ValueSizeCalculator { get; }
380 381 382 383 384

        /// <summary>
        /// Returns a delegate to read a value from a coded input stream. It is assumed that
        /// the stream is already positioned on the appropriate tag.
        /// </summary>
385
        internal Func<CodedInputStream, T> ValueReader { get; }
386 387 388 389

        /// <summary>
        /// Returns the fixed size for an entry, or 0 if sizes vary.
        /// </summary>
390
        internal int FixedSize { get; }
391

392 393 394 395 396 397
        /// <summary>
        /// Gets the tag of the codec.
        /// </summary>
        /// <value>
        /// The tag of the codec.
        /// </value>
398
        internal uint Tag { get; }
Jon Skeet's avatar
Jon Skeet committed
399

400
        /// <summary>
401 402 403
        /// 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.
404 405 406 407
        /// </summary>
        /// <value>
        /// The default value of the codec's type.
        /// </value>
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
        internal T DefaultValue { get; }

        private readonly int tagSize;
        
        internal FieldCodec(
                Func<CodedInputStream, T> reader,
                Action<CodedOutputStream, T> writer,
                int fixedSize,
                uint tag) : this(reader, writer, _ => fixedSize, tag)
        {
            FixedSize = fixedSize;
        }

        internal FieldCodec(
            Func<CodedInputStream, T> reader,
            Action<CodedOutputStream, T> writer,
            Func<T, int> sizeCalculator,
            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)
        {
            ValueReader = reader;
            ValueWriter = writer;
            ValueSizeCalculator = sizeCalculator;
            FixedSize = 0;
            Tag = tag;
            DefaultValue = defaultValue;
            tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
            // Detect packed-ness once, so we can check for it within RepeatedField<T>.
            PackedRepeatedField = IsPackedRepeatedField(tag);
        }
Jon Skeet's avatar
Jon Skeet committed
446

447 448 449 450
        /// <summary>
        /// Write a tag and the given value, *if* the value is not the default.
        /// </summary>
        public void WriteTagAndValue(CodedOutputStream output, T value)
Jon Skeet's avatar
Jon Skeet committed
451 452 453
        {
            if (!IsDefault(value))
            {
454 455
                output.WriteTag(Tag);
                ValueWriter(output, value);
Jon Skeet's avatar
Jon Skeet committed
456 457 458
            }
        }

459 460 461 462 463
        /// <summary>
        /// Reads a value of the codec type from the given <see cref="CodedInputStream"/>.
        /// </summary>
        /// <param name="input">The input stream to read from.</param>
        /// <returns>The value read from the stream.</returns>
464
        public T Read(CodedInputStream input) => ValueReader(input);
Jon Skeet's avatar
Jon Skeet committed
465

466 467 468 469
        /// <summary>
        /// Calculates the size required to write the given value, with a tag,
        /// if the value is not the default.
        /// </summary>
470
        public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
471

472
        private bool IsDefault(T value) => EqualityComparer<T>.Default.Equals(value, DefaultValue);
Jon Skeet's avatar
Jon Skeet committed
473 474
    }
}