• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using Google.Protobuf.Compatibility;
34 using Google.Protobuf.WellKnownTypes;
35 using System;
36 using System.Collections.Generic;
37 
38 namespace Google.Protobuf
39 {
40     /// <summary>
41     /// Factory methods for <see cref="FieldCodec{T}"/>.
42     /// </summary>
43     public static class FieldCodec
44     {
45         // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
46 
47         /// <summary>
48         /// Retrieves a codec suitable for a string field with the given tag.
49         /// </summary>
50         /// <param name="tag">The tag.</param>
51         /// <returns>A codec for the given tag.</returns>
ForString(uint tag)52         public static FieldCodec<string> ForString(uint tag)
53         {
54             return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
55         }
56 
57         /// <summary>
58         /// Retrieves a codec suitable for a bytes field with the given tag.
59         /// </summary>
60         /// <param name="tag">The tag.</param>
61         /// <returns>A codec for the given tag.</returns>
ForBytes(uint tag)62         public static FieldCodec<ByteString> ForBytes(uint tag)
63         {
64             return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag);
65         }
66 
67         /// <summary>
68         /// Retrieves a codec suitable for a bool field with the given tag.
69         /// </summary>
70         /// <param name="tag">The tag.</param>
71         /// <returns>A codec for the given tag.</returns>
ForBool(uint tag)72         public static FieldCodec<bool> ForBool(uint tag)
73         {
74             return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag);
75         }
76 
77         /// <summary>
78         /// Retrieves a codec suitable for an int32 field with the given tag.
79         /// </summary>
80         /// <param name="tag">The tag.</param>
81         /// <returns>A codec for the given tag.</returns>
ForInt32(uint tag)82         public static FieldCodec<int> ForInt32(uint tag)
83         {
84             return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag);
85         }
86 
87         /// <summary>
88         /// Retrieves a codec suitable for an sint32 field with the given tag.
89         /// </summary>
90         /// <param name="tag">The tag.</param>
91         /// <returns>A codec for the given tag.</returns>
ForSInt32(uint tag)92         public static FieldCodec<int> ForSInt32(uint tag)
93         {
94             return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag);
95         }
96 
97         /// <summary>
98         /// Retrieves a codec suitable for a fixed32 field with the given tag.
99         /// </summary>
100         /// <param name="tag">The tag.</param>
101         /// <returns>A codec for the given tag.</returns>
ForFixed32(uint tag)102         public static FieldCodec<uint> ForFixed32(uint tag)
103         {
104             return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag);
105         }
106 
107         /// <summary>
108         /// Retrieves a codec suitable for an sfixed32 field with the given tag.
109         /// </summary>
110         /// <param name="tag">The tag.</param>
111         /// <returns>A codec for the given tag.</returns>
ForSFixed32(uint tag)112         public static FieldCodec<int> ForSFixed32(uint tag)
113         {
114             return new FieldCodec<int>(input => input.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag);
115         }
116 
117         /// <summary>
118         /// Retrieves a codec suitable for a uint32 field with the given tag.
119         /// </summary>
120         /// <param name="tag">The tag.</param>
121         /// <returns>A codec for the given tag.</returns>
ForUInt32(uint tag)122         public static FieldCodec<uint> ForUInt32(uint tag)
123         {
124             return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag);
125         }
126 
127         /// <summary>
128         /// Retrieves a codec suitable for an int64 field with the given tag.
129         /// </summary>
130         /// <param name="tag">The tag.</param>
131         /// <returns>A codec for the given tag.</returns>
ForInt64(uint tag)132         public static FieldCodec<long> ForInt64(uint tag)
133         {
134             return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag);
135         }
136 
137         /// <summary>
138         /// Retrieves a codec suitable for an sint64 field with the given tag.
139         /// </summary>
140         /// <param name="tag">The tag.</param>
141         /// <returns>A codec for the given tag.</returns>
ForSInt64(uint tag)142         public static FieldCodec<long> ForSInt64(uint tag)
143         {
144             return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag);
145         }
146 
147         /// <summary>
148         /// Retrieves a codec suitable for a fixed64 field with the given tag.
149         /// </summary>
150         /// <param name="tag">The tag.</param>
151         /// <returns>A codec for the given tag.</returns>
ForFixed64(uint tag)152         public static FieldCodec<ulong> ForFixed64(uint tag)
153         {
154             return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag);
155         }
156 
157         /// <summary>
158         /// Retrieves a codec suitable for an sfixed64 field with the given tag.
159         /// </summary>
160         /// <param name="tag">The tag.</param>
161         /// <returns>A codec for the given tag.</returns>
ForSFixed64(uint tag)162         public static FieldCodec<long> ForSFixed64(uint tag)
163         {
164             return new FieldCodec<long>(input => input.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag);
165         }
166 
167         /// <summary>
168         /// Retrieves a codec suitable for a uint64 field with the given tag.
169         /// </summary>
170         /// <param name="tag">The tag.</param>
171         /// <returns>A codec for the given tag.</returns>
ForUInt64(uint tag)172         public static FieldCodec<ulong> ForUInt64(uint tag)
173         {
174             return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag);
175         }
176 
177         /// <summary>
178         /// Retrieves a codec suitable for a float field with the given tag.
179         /// </summary>
180         /// <param name="tag">The tag.</param>
181         /// <returns>A codec for the given tag.</returns>
ForFloat(uint tag)182         public static FieldCodec<float> ForFloat(uint tag)
183         {
184             return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag);
185         }
186 
187         /// <summary>
188         /// Retrieves a codec suitable for a double field with the given tag.
189         /// </summary>
190         /// <param name="tag">The tag.</param>
191         /// <returns>A codec for the given tag.</returns>
ForDouble(uint tag)192         public static FieldCodec<double> ForDouble(uint tag)
193         {
194             return new FieldCodec<double>(input => input.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag);
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         /// <summary>
201         /// Retrieves a codec suitable for an enum field with the given tag.
202         /// </summary>
203         /// <param name="tag">The tag.</param>
204         /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
205         /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
206         /// <returns>A codec for the given tag.</returns>
ForEnum(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)207         public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
208         {
209             return new FieldCodec<T>(input => fromInt32(
210                 input.ReadEnum()),
211                 (output, value) => output.WriteEnum(toInt32(value)),
212                 value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag);
213         }
214 
215         /// <summary>
216         /// Retrieves a codec suitable for a message field with the given tag.
217         /// </summary>
218         /// <param name="tag">The tag.</param>
219         /// <param name="parser">A parser to use for the message type.</param>
220         /// <returns>A codec for the given tag.</returns>
221         public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : IMessage<T>
222         {
223             return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
224                 (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
225         }
226 
227         /// <summary>
228         /// Creates a codec for a wrapper type of a class - which must be string or ByteString.
229         /// </summary>
230         public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class
231         {
232             var nestedCodec = WrapperCodecs.GetCodec<T>();
233             return new FieldCodec<T>(
234                 input => WrapperCodecs.Read<T>(input, nestedCodec),
235                 (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
236                 value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
237                 tag,
238                 null); // Default value for the wrapper
239         }
240 
241         /// <summary>
242         /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64,
243         /// Bool, Single or Double.
244         /// </summary>
245         public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct
246         {
247             var nestedCodec = WrapperCodecs.GetCodec<T>();
248             return new FieldCodec<T?>(
249                 input => WrapperCodecs.Read<T>(input, nestedCodec),
250                 (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
251                 value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
252                 tag,
253                 null); // Default value for the wrapper
254         }
255 
256         /// <summary>
257         /// Helper code to create codecs for wrapper types.
258         /// </summary>
259         /// <remarks>
260         /// Somewhat ugly with all the static methods, but the conversions involved to/from nullable types make it
261         /// slightly tricky to improve. So long as we keep the public API (ForClassWrapper, ForStructWrapper) in place,
262         /// we can refactor later if we come up with something cleaner.
263         /// </remarks>
264         private static class WrapperCodecs
265         {
266             private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object>
267             {
268                 { typeof(bool), ForBool(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
269                 { typeof(int), ForInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
270                 { typeof(long), ForInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
271                 { typeof(uint), ForUInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
272                 { typeof(ulong), ForUInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
273                 { typeof(float), ForFloat(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed32)) },
274                 { typeof(double), ForDouble(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed64)) },
275                 { typeof(string), ForString(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) },
276                 { typeof(ByteString), ForBytes(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) }
277             };
278 
279             /// <summary>
280             /// Returns a field codec which effectively wraps a value of type T in a message.
281             ///
282             /// </summary>
GetCodec()283             internal static FieldCodec<T> GetCodec<T>()
284             {
285                 object value;
286                 if (!Codecs.TryGetValue(typeof(T), out value))
287                 {
288                     throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
289                 }
290                 return (FieldCodec<T>) value;
291             }
292 
Read(CodedInputStream input, FieldCodec<T> codec)293             internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec)
294             {
295                 int length = input.ReadLength();
296                 int oldLimit = input.PushLimit(length);
297 
298                 uint tag;
299                 T value = codec.DefaultValue;
300                 while ((tag = input.ReadTag()) != 0)
301                 {
302                     if (tag == codec.Tag)
303                     {
304                         value = codec.Read(input);
305                     }
306                     else
307                     {
308                         input.SkipLastField();
309                     }
310 
311                 }
312                 input.CheckReadEndOfStreamTag();
313                 input.PopLimit(oldLimit);
314 
315                 return value;
316             }
317 
Write(CodedOutputStream output, T value, FieldCodec<T> codec)318             internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
319             {
320                 output.WriteLength(codec.CalculateSizeWithTag(value));
321                 codec.WriteTagAndValue(output, value);
322             }
323 
CalculateSize(T value, FieldCodec<T> codec)324             internal  static int CalculateSize<T>(T value, FieldCodec<T> codec)
325             {
326                 int fieldLength = codec.CalculateSizeWithTag(value);
327                 return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
328             }
329         }
330     }
331 
332     /// <summary>
333     /// <para>
334     /// An encode/decode pair for a single field. This effectively encapsulates
335     /// all the information needed to read or write the field value from/to a coded
336     /// stream.
337     /// </para>
338     /// <para>
339     /// This class is public and has to be as it is used by generated code, but its public
340     /// API is very limited - just what the generated code needs to call directly.
341     /// </para>
342     /// </summary>
343     /// <remarks>
344     /// This never writes default values to the stream, and does not address "packedness"
345     /// in repeated fields itself, other than to know whether or not the field *should* be packed.
346     /// </remarks>
347     public sealed class FieldCodec<T>
348     {
349         private static readonly T DefaultDefault;
350         private static readonly bool TypeSupportsPacking = typeof(T).IsValueType() && Nullable.GetUnderlyingType(typeof(T)) == null;
351 
FieldCodec()352         static FieldCodec()
353         {
354             if (typeof(T) == typeof(string))
355             {
356                 DefaultDefault = (T)(object)"";
357             }
358             else if (typeof(T) == typeof(ByteString))
359             {
360                 DefaultDefault = (T)(object)ByteString.Empty;
361             }
362             // Otherwise it's the default value of the CLR type
363         }
364 
IsPackedRepeatedField(uint tag)365         internal static bool IsPackedRepeatedField(uint tag) =>
366             TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited;
367 
368         internal bool PackedRepeatedField { get; }
369 
370         /// <summary>
371         /// Returns a delegate to write a value (unconditionally) to a coded output stream.
372         /// </summary>
373         internal Action<CodedOutputStream, T> ValueWriter { get; }
374 
375         /// <summary>
376         /// Returns the size calculator for just a value.
377         /// </summary>
378         internal Func<T, int> ValueSizeCalculator { get; }
379 
380         /// <summary>
381         /// Returns a delegate to read a value from a coded input stream. It is assumed that
382         /// the stream is already positioned on the appropriate tag.
383         /// </summary>
384         internal Func<CodedInputStream, T> ValueReader { get; }
385 
386         /// <summary>
387         /// Returns the fixed size for an entry, or 0 if sizes vary.
388         /// </summary>
389         internal int FixedSize { get; }
390 
391         /// <summary>
392         /// Gets the tag of the codec.
393         /// </summary>
394         /// <value>
395         /// The tag of the codec.
396         /// </value>
397         internal uint Tag { get; }
398 
399         /// <summary>
400         /// Default value for this codec. Usually the same for every instance of the same type, but
401         /// for string/ByteString wrapper fields the codec's default value is null, whereas for
402         /// other string/ByteString fields it's "" or ByteString.Empty.
403         /// </summary>
404         /// <value>
405         /// The default value of the codec's type.
406         /// </value>
407         internal T DefaultValue { get; }
408 
409         private readonly int tagSize;
410 
FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, int fixedSize, uint tag)411         internal FieldCodec(
412                 Func<CodedInputStream, T> reader,
413                 Action<CodedOutputStream, T> writer,
414                 int fixedSize,
415                 uint tag) : this(reader, writer, _ => fixedSize, tag)
416         {
417             FixedSize = fixedSize;
418         }
419 
FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, Func<T, int> sizeCalculator, uint tag)420         internal FieldCodec(
421             Func<CodedInputStream, T> reader,
422             Action<CodedOutputStream, T> writer,
423             Func<T, int> sizeCalculator,
424             uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault)
425         {
426         }
427 
FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, Func<T, int> sizeCalculator, uint tag, T defaultValue)428         internal FieldCodec(
429             Func<CodedInputStream, T> reader,
430             Action<CodedOutputStream, T> writer,
431             Func<T, int> sizeCalculator,
432             uint tag,
433             T defaultValue)
434         {
435             ValueReader = reader;
436             ValueWriter = writer;
437             ValueSizeCalculator = sizeCalculator;
438             FixedSize = 0;
439             Tag = tag;
440             DefaultValue = defaultValue;
441             tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
442             // Detect packed-ness once, so we can check for it within RepeatedField<T>.
443             PackedRepeatedField = IsPackedRepeatedField(tag);
444         }
445 
446         /// <summary>
447         /// Write a tag and the given value, *if* the value is not the default.
448         /// </summary>
WriteTagAndValue(CodedOutputStream output, T value)449         public void WriteTagAndValue(CodedOutputStream output, T value)
450         {
451             if (!IsDefault(value))
452             {
453                 output.WriteTag(Tag);
454                 ValueWriter(output, value);
455             }
456         }
457 
458         /// <summary>
459         /// Reads a value of the codec type from the given <see cref="CodedInputStream"/>.
460         /// </summary>
461         /// <param name="input">The input stream to read from.</param>
462         /// <returns>The value read from the stream.</returns>
463         public T Read(CodedInputStream input) => ValueReader(input);
464 
465         /// <summary>
466         /// Calculates the size required to write the given value, with a tag,
467         /// if the value is not the default.
468         /// </summary>
469         public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
470 
471         private bool IsDefault(T value) => EqualityComparer<T>.Default.Equals(value, DefaultValue);
472     }
473 }
474