chore: protobuf and newston.json
parent
b10b0c3af7
commit
a9f1f8772d
|
@ -0,0 +1,79 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a utility routine to copy small arrays much more quickly than Buffer.BlockCopy
|
||||
/// </summary>
|
||||
internal static class ByteArray
|
||||
{
|
||||
/// <summary>
|
||||
/// The threshold above which you should use Buffer.BlockCopy rather than ByteArray.Copy
|
||||
/// </summary>
|
||||
private const int CopyThreshold = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Determines which copy routine to use based on the number of bytes to be copied.
|
||||
/// </summary>
|
||||
internal static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
|
||||
{
|
||||
if (count > CopyThreshold)
|
||||
{
|
||||
Buffer.BlockCopy(src, srcOffset, dst, dstOffset, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
int stop = srcOffset + count;
|
||||
for (int i = srcOffset; i < stop; i++)
|
||||
{
|
||||
dst[dstOffset++] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the order of bytes in the array
|
||||
/// </summary>
|
||||
internal static void Reverse(byte[] bytes)
|
||||
{
|
||||
for (int first = 0, last = bytes.Length - 1; first < last; first++, last--)
|
||||
{
|
||||
byte temp = bytes[first];
|
||||
bytes[first] = bytes[last];
|
||||
bytes[last] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
#if !NET35
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
#if NET35
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
#endif
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Immutable array of bytes.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public sealed class ByteString : IEnumerable<byte>, IEquatable<ByteString>
|
||||
{
|
||||
private static readonly ByteString empty = new ByteString(new byte[0]);
|
||||
|
||||
private readonly ReadOnlyMemory<byte> bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Internal use only. Ensure that the provided memory is not mutated and belongs to this instance.
|
||||
/// </summary>
|
||||
internal static ByteString AttachBytes(ReadOnlyMemory<byte> bytes)
|
||||
{
|
||||
return new ByteString(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal use only. Ensure that the provided memory is not mutated and belongs to this instance.
|
||||
/// This method encapsulates converting array to memory. Reduces need for SecuritySafeCritical
|
||||
/// in .NET Framework.
|
||||
/// </summary>
|
||||
internal static ByteString AttachBytes(byte[] bytes)
|
||||
{
|
||||
return AttachBytes(bytes.AsMemory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ByteString from the given memory. The memory is
|
||||
/// *not* copied, and must not be modified after this constructor is called.
|
||||
/// </summary>
|
||||
private ByteString(ReadOnlyMemory<byte> bytes)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an empty ByteString.
|
||||
/// </summary>
|
||||
public static ByteString Empty
|
||||
{
|
||||
get { return empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the length of this ByteString in bytes.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get { return bytes.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this byte string is empty, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
get { return Length == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides read-only access to the data of this <see cref="ByteString"/>.
|
||||
/// No data is copied so this is the most efficient way of accessing.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<byte> Span
|
||||
{
|
||||
get { return bytes.Span; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides read-only access to the data of this <see cref="ByteString"/>.
|
||||
/// No data is copied so this is the most efficient way of accessing.
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Memory
|
||||
{
|
||||
get { return bytes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="ByteString"/> into a byte array.
|
||||
/// </summary>
|
||||
/// <remarks>The data is copied - changes to the returned array will not be reflected in this <c>ByteString</c>.</remarks>
|
||||
/// <returns>A byte array with the same data as this <c>ByteString</c>.</returns>
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="ByteString"/> into a standard base64 representation.
|
||||
/// </summary>
|
||||
/// <returns>A base64 representation of this <c>ByteString</c>.</returns>
|
||||
public string ToBase64()
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
||||
{
|
||||
// Fast path. ByteString was created with an array, so pass the underlying array.
|
||||
return Convert.ToBase64String(segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow path. BytesString is not an array. Convert memory and pass result to ToBase64String.
|
||||
return Convert.ToBase64String(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString" /> from the Base64 Encoded String.
|
||||
/// </summary>
|
||||
public static ByteString FromBase64(string bytes)
|
||||
{
|
||||
// By handling the empty string explicitly, we not only optimize but we fix a
|
||||
// problem on CF 2.0. See issue 61 for details.
|
||||
return bytes == "" ? Empty : new ByteString(Convert.FromBase64String(bytes));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString"/> from data in the given stream, synchronously.
|
||||
/// </summary>
|
||||
/// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position
|
||||
/// at the start of the call.</remarks>
|
||||
/// <param name="stream">The stream to copy into a ByteString.</param>
|
||||
/// <returns>A ByteString with content read from the given stream.</returns>
|
||||
public static ByteString FromStream(Stream stream)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
|
||||
int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0;
|
||||
var memoryStream = new MemoryStream(capacity);
|
||||
stream.CopyTo(memoryStream);
|
||||
#if NETSTANDARD1_1 || NETSTANDARD2_0
|
||||
byte[] bytes = memoryStream.ToArray();
|
||||
#else
|
||||
// Avoid an extra copy if we can.
|
||||
byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
||||
#endif
|
||||
return AttachBytes(bytes);
|
||||
}
|
||||
|
||||
#if !NET35
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString"/> from data in the given stream, asynchronously.
|
||||
/// </summary>
|
||||
/// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position
|
||||
/// at the start of the call.</remarks>
|
||||
/// <param name="stream">The stream to copy into a ByteString.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param>
|
||||
/// <returns>A ByteString with content read from the given stream.</returns>
|
||||
public static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
|
||||
return ByteStringAsync.FromStreamAsyncCore(stream, cancellationToken);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString" /> from the given array. The contents
|
||||
/// are copied, so further modifications to the array will not
|
||||
/// be reflected in the returned ByteString.
|
||||
/// This method can also be invoked in <c>ByteString.CopyFrom(0xaa, 0xbb, ...)</c> form
|
||||
/// which is primarily useful for testing.
|
||||
/// </summary>
|
||||
public static ByteString CopyFrom(params byte[] bytes)
|
||||
{
|
||||
return new ByteString((byte[]) bytes.Clone());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString" /> from a portion of a byte array.
|
||||
/// </summary>
|
||||
public static ByteString CopyFrom(byte[] bytes, int offset, int count)
|
||||
{
|
||||
byte[] portion = new byte[count];
|
||||
ByteArray.Copy(bytes, offset, portion, 0, count);
|
||||
return new ByteString(portion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteString" /> from a read only span. The contents
|
||||
/// are copied, so further modifications to the span will not
|
||||
/// be reflected in the returned <see cref="ByteString" />.
|
||||
/// </summary>
|
||||
public static ByteString CopyFrom(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
return new ByteString(bytes.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ByteString" /> by encoding the specified text with
|
||||
/// the given encoding.
|
||||
/// </summary>
|
||||
public static ByteString CopyFrom(string text, Encoding encoding)
|
||||
{
|
||||
return new ByteString(encoding.GetBytes(text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ByteString" /> by encoding the specified text in UTF-8.
|
||||
/// </summary>
|
||||
public static ByteString CopyFromUtf8(string text)
|
||||
{
|
||||
return CopyFrom(text, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the byte at the given index.
|
||||
/// </summary>
|
||||
public byte this[int index]
|
||||
{
|
||||
get { return bytes.Span[index]; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="ByteString"/> into a string by applying the given encoding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should only be used to convert binary data which was the result of encoding
|
||||
/// text with the given encoding.
|
||||
/// </remarks>
|
||||
/// <param name="encoding">The encoding to use to decode the binary data into text.</param>
|
||||
/// <returns>The result of decoding the binary data with the given decoding.</returns>
|
||||
public string ToString(Encoding encoding)
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
||||
{
|
||||
// Fast path. ByteString was created with an array.
|
||||
return encoding.GetString(segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow path. BytesString is not an array. Convert memory and pass result to GetString.
|
||||
// TODO: Consider using GetString overload that takes a pointer.
|
||||
byte[] array = bytes.ToArray();
|
||||
return encoding.GetString(array, 0, array.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="ByteString"/> into a string by applying the UTF-8 encoding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should only be used to convert binary data which was the result of encoding
|
||||
/// text with UTF-8.
|
||||
/// </remarks>
|
||||
/// <returns>The result of decoding the binary data with the given decoding.</returns>
|
||||
public string ToStringUtf8()
|
||||
{
|
||||
return ToString(Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
|
||||
/// </summary>
|
||||
/// <returns>An iterator over the bytes in this object.</returns>
|
||||
[SecuritySafeCritical]
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
return MemoryMarshal.ToEnumerable(bytes).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
|
||||
/// </summary>
|
||||
/// <returns>An iterator over the bytes in this object.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CodedInputStream from this ByteString's data.
|
||||
/// </summary>
|
||||
public CodedInputStream CreateCodedInput()
|
||||
{
|
||||
// We trust CodedInputStream not to reveal the provided byte array or modify it
|
||||
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment) && segment.Count == bytes.Length)
|
||||
{
|
||||
// Fast path. ByteString was created with a complete array.
|
||||
return new CodedInputStream(segment.Array);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow path. BytesString is not an array, or is a slice of an array.
|
||||
// Convert memory and pass result to WriteRawBytes.
|
||||
return new CodedInputStream(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two byte strings for equality.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The first byte string to compare.</param>
|
||||
/// <param name="rhs">The second byte string to compare.</param>
|
||||
/// <returns><c>true</c> if the byte strings are equal; false otherwise.</returns>
|
||||
public static bool operator ==(ByteString lhs, ByteString rhs)
|
||||
{
|
||||
if (ReferenceEquals(lhs, rhs))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs.bytes.Span.SequenceEqual(rhs.bytes.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two byte strings for inequality.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The first byte string to compare.</param>
|
||||
/// <param name="rhs">The second byte string to compare.</param>
|
||||
/// <returns><c>false</c> if the byte strings are equal; true otherwise.</returns>
|
||||
public static bool operator !=(ByteString lhs, ByteString rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this byte string with another object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="obj"/> refers to an equal <see cref="ByteString"/>; <c>false</c> otherwise.</returns>
|
||||
[SecuritySafeCritical]
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this == (obj as ByteString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this object. Two equal byte strings
|
||||
/// will return the same hash code.
|
||||
/// </summary>
|
||||
/// <returns>A hash code for this object.</returns>
|
||||
[SecuritySafeCritical]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
ReadOnlySpan<byte> b = bytes.Span;
|
||||
|
||||
int ret = 23;
|
||||
for (int i = 0; i < b.Length; i++)
|
||||
{
|
||||
ret = (ret * 31) + b[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this byte string with another.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="ByteString"/> to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal byte string; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(ByteString other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire byte array to the destination array provided at the offset specified.
|
||||
/// </summary>
|
||||
public void CopyTo(byte[] array, int position)
|
||||
{
|
||||
bytes.CopyTo(array.AsMemory(position));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the entire byte array to the provided stream
|
||||
/// </summary>
|
||||
public void WriteTo(Stream outputStream)
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
||||
{
|
||||
// Fast path. ByteString was created with an array, so pass the underlying array.
|
||||
outputStream.Write(segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow path. BytesString is not an array. Convert memory and pass result to WriteRawBytes.
|
||||
var array = bytes.ToArray();
|
||||
outputStream.Write(array, 0, array.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// SecuritySafeCritical attribute can not be placed on types with async methods.
|
||||
/// This class has ByteString's async methods so it can be marked with SecuritySafeCritical.
|
||||
/// </summary>
|
||||
internal static class ByteStringAsync
|
||||
{
|
||||
#if !NET35
|
||||
internal static async Task<ByteString> FromStreamAsyncCore(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
int capacity = stream.CanSeek ? checked((int)(stream.Length - stream.Position)) : 0;
|
||||
var memoryStream = new MemoryStream(capacity);
|
||||
// We have to specify the buffer size here, as there's no overload accepting the cancellation token
|
||||
// alone. But it's documented to use 81920 by default if not specified.
|
||||
await stream.CopyToAsync(memoryStream, 81920, cancellationToken);
|
||||
#if NETSTANDARD1_1
|
||||
byte[] bytes = memoryStream.ToArray();
|
||||
#else
|
||||
// Avoid an extra copy if we can.
|
||||
byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
||||
#endif
|
||||
return ByteString.AttachBytes(bytes);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,699 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads and decodes protocol message fields.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class is generally used by generated code to read appropriate
|
||||
/// primitives from the stream. It effectively encapsulates the lowest
|
||||
/// levels of protocol buffer format.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Repeated fields and map fields are not handled by this class; use <see cref="RepeatedField{T}"/>
|
||||
/// and <see cref="MapField{TKey, TValue}"/> to serialize such fields.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
public sealed class CodedInputStream : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to leave the underlying stream open when disposing of this stream.
|
||||
/// This is always true when there's no stream.
|
||||
/// </summary>
|
||||
private readonly bool leaveOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer of data read from the stream or provided at construction time.
|
||||
/// </summary>
|
||||
private readonly byte[] buffer;
|
||||
|
||||
/// <summary>
|
||||
/// The stream to read further input from, or null if the byte array buffer was provided
|
||||
/// directly on construction, with no further data available.
|
||||
/// </summary>
|
||||
private readonly Stream input;
|
||||
|
||||
/// <summary>
|
||||
/// The parser state is kept separately so that other parse implementations can reuse the same
|
||||
/// parsing primitives.
|
||||
/// </summary>
|
||||
private ParserInternalState state;
|
||||
|
||||
internal const int DefaultRecursionLimit = 100;
|
||||
internal const int DefaultSizeLimit = Int32.MaxValue;
|
||||
internal const int BufferSize = 4096;
|
||||
|
||||
#region Construction
|
||||
// Note that the checks are performed such that we don't end up checking obviously-valid things
|
||||
// like non-null references for arrays we've just created.
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedInputStream reading data from the given byte array.
|
||||
/// </summary>
|
||||
public CodedInputStream(byte[] buffer) : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), 0, buffer.Length, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CodedInputStream"/> that reads from the given byte array slice.
|
||||
/// </summary>
|
||||
public CodedInputStream(byte[] buffer, int offset, int length)
|
||||
: this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), offset, offset + length, true)
|
||||
{
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("offset", "Offset must be within the buffer");
|
||||
}
|
||||
if (length < 0 || offset + length > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length", "Length must be non-negative and within the buffer");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CodedInputStream"/> reading data from the given stream, which will be disposed
|
||||
/// when the returned object is disposed.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to read from.</param>
|
||||
public CodedInputStream(Stream input) : this(input, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CodedInputStream"/> reading data from the given stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to read from.</param>
|
||||
/// <param name="leaveOpen"><c>true</c> to leave <paramref name="input"/> open when the returned
|
||||
/// <c cref="CodedInputStream"/> is disposed; <c>false</c> to dispose of the given stream when the
|
||||
/// returned object is disposed.</param>
|
||||
public CodedInputStream(Stream input, bool leaveOpen)
|
||||
: this(ProtoPreconditions.CheckNotNull(input, "input"), new byte[BufferSize], 0, 0, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedInputStream reading data from the given
|
||||
/// stream and buffer, using the default limits.
|
||||
/// </summary>
|
||||
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, bool leaveOpen)
|
||||
{
|
||||
this.input = input;
|
||||
this.buffer = buffer;
|
||||
this.state.bufferPos = bufferPos;
|
||||
this.state.bufferSize = bufferSize;
|
||||
this.state.sizeLimit = DefaultSizeLimit;
|
||||
this.state.recursionLimit = DefaultRecursionLimit;
|
||||
SegmentedBufferHelper.Initialize(this, out this.state.segmentedBufferHelper);
|
||||
this.leaveOpen = leaveOpen;
|
||||
|
||||
this.state.currentLimit = int.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedInputStream reading data from the given
|
||||
/// stream and buffer, using the specified limits.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This chains to the version with the default limits instead of vice versa to avoid
|
||||
/// having to check that the default values are valid every time.
|
||||
/// </remarks>
|
||||
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, int sizeLimit, int recursionLimit, bool leaveOpen)
|
||||
: this(input, buffer, bufferPos, bufferSize, leaveOpen)
|
||||
{
|
||||
if (sizeLimit <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("sizeLimit", "Size limit must be positive");
|
||||
}
|
||||
if (recursionLimit <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("recursionLimit!", "Recursion limit must be positive");
|
||||
}
|
||||
this.state.sizeLimit = sizeLimit;
|
||||
this.state.recursionLimit = recursionLimit;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CodedInputStream"/> with the specified size and recursion limits, reading
|
||||
/// from an input stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method exists separately from the constructor to reduce the number of constructor overloads.
|
||||
/// It is likely to be used considerably less frequently than the constructors, as the default limits
|
||||
/// are suitable for most use cases.
|
||||
/// </remarks>
|
||||
/// <param name="input">The input stream to read from</param>
|
||||
/// <param name="sizeLimit">The total limit of data to read from the stream.</param>
|
||||
/// <param name="recursionLimit">The maximum recursion depth to allow while reading.</param>
|
||||
/// <returns>A <c>CodedInputStream</c> reading from <paramref name="input"/> with the specified size
|
||||
/// and recursion limits.</returns>
|
||||
public static CodedInputStream CreateWithLimits(Stream input, int sizeLimit, int recursionLimit)
|
||||
{
|
||||
// Note: we may want an overload accepting leaveOpen
|
||||
return new CodedInputStream(input, new byte[BufferSize], 0, 0, sizeLimit, recursionLimit, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current position in the input stream, or the position in the input buffer
|
||||
/// </summary>
|
||||
public long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
return input.Position - ((state.bufferSize + state.bufferSizeAfterLimit) - state.bufferPos);
|
||||
}
|
||||
return state.bufferPos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last tag read, or 0 if no tags have been read or we've read beyond
|
||||
/// the end of the stream.
|
||||
/// </summary>
|
||||
internal uint LastTag { get { return state.lastTag; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size limit for this stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This limit is applied when reading from the underlying stream, as a sanity check. It is
|
||||
/// not applied when reading from a byte array data source without an underlying stream.
|
||||
/// The default value is Int32.MaxValue.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// The size limit.
|
||||
/// </value>
|
||||
public int SizeLimit { get { return state.sizeLimit; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the recursion limit for this stream. This limit is applied whilst reading messages,
|
||||
/// to avoid maliciously-recursive data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default limit is 100.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// The recursion limit for this stream.
|
||||
/// </value>
|
||||
public int RecursionLimit { get { return state.recursionLimit; } }
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
|
||||
/// </summary>
|
||||
internal bool DiscardUnknownFields
|
||||
{
|
||||
get { return state.DiscardUnknownFields; }
|
||||
set { state.DiscardUnknownFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
|
||||
/// </summary>
|
||||
internal ExtensionRegistry ExtensionRegistry
|
||||
{
|
||||
get { return state.ExtensionRegistry; }
|
||||
set { state.ExtensionRegistry = value; }
|
||||
}
|
||||
|
||||
internal byte[] InternalBuffer => buffer;
|
||||
|
||||
internal Stream InternalInputStream => input;
|
||||
|
||||
internal ref ParserInternalState InternalState => ref state;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of this instance, potentially closing any underlying stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As there is no flushing to perform here, disposing of a <see cref="CodedInputStream"/> which
|
||||
/// was constructed with the <c>leaveOpen</c> option parameter set to <c>true</c> (or one which
|
||||
/// was constructed to read from a byte array) has no effect.
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!leaveOpen)
|
||||
{
|
||||
input.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Validation
|
||||
/// <summary>
|
||||
/// Verifies that the last call to ReadTag() returned tag 0 - in other words,
|
||||
/// we've reached the end of the stream when we expected to.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">The
|
||||
/// tag read was not the one specified</exception>
|
||||
internal void CheckReadEndOfStreamTag()
|
||||
{
|
||||
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref state);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Reading of tags etc
|
||||
|
||||
/// <summary>
|
||||
/// Peeks at the next field tag. This is like calling <see cref="ReadTag"/>, but the
|
||||
/// tag is not consumed. (So a subsequent call to <see cref="ReadTag"/> will return the
|
||||
/// same value.)
|
||||
/// </summary>
|
||||
public uint PeekTag()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.PeekTag(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a field tag, returning the tag of 0 for "end of stream".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this method returns 0, it doesn't necessarily mean the end of all
|
||||
/// the data in this CodedInputStream; it may be the end of the logical stream
|
||||
/// for an embedded message, for example.
|
||||
/// </remarks>
|
||||
/// <returns>The next field tag, or 0 for end of stream. (0 is never a valid tag.)</returns>
|
||||
public uint ReadTag()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseTag(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips the data for the field with the tag we've just read.
|
||||
/// This should be called directly after <see cref="ReadTag"/>, when
|
||||
/// the caller wishes to skip an unknown field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method throws <see cref="InvalidProtocolBufferException"/> if the last-read tag was an end-group tag.
|
||||
/// If a caller wishes to skip a group, they should skip the whole group, by calling this method after reading the
|
||||
/// start-group tag. This behavior allows callers to call this method on any field they don't understand, correctly
|
||||
/// resulting in an error if an end-group tag has not been paired with an earlier start-group tag.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidProtocolBufferException">The last tag was an end-group tag</exception>
|
||||
/// <exception cref="InvalidOperationException">The last read operation read to the end of the logical stream</exception>
|
||||
public void SkipLastField()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
ParsingPrimitivesMessages.SkipLastField(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip a group.
|
||||
/// </summary>
|
||||
internal void SkipGroup(uint startGroupTag)
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
ParsingPrimitivesMessages.SkipGroup(ref span, ref state, startGroupTag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a double field from the stream.
|
||||
/// </summary>
|
||||
public double ReadDouble()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseDouble(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a float field from the stream.
|
||||
/// </summary>
|
||||
public float ReadFloat()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseFloat(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a uint64 field from the stream.
|
||||
/// </summary>
|
||||
public ulong ReadUInt64()
|
||||
{
|
||||
return ReadRawVarint64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int64 field from the stream.
|
||||
/// </summary>
|
||||
public long ReadInt64()
|
||||
{
|
||||
return (long) ReadRawVarint64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int32 field from the stream.
|
||||
/// </summary>
|
||||
public int ReadInt32()
|
||||
{
|
||||
return (int) ReadRawVarint32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed64 field from the stream.
|
||||
/// </summary>
|
||||
public ulong ReadFixed64()
|
||||
{
|
||||
return ReadRawLittleEndian64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed32 field from the stream.
|
||||
/// </summary>
|
||||
public uint ReadFixed32()
|
||||
{
|
||||
return ReadRawLittleEndian32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bool field from the stream.
|
||||
/// </summary>
|
||||
public bool ReadBool()
|
||||
{
|
||||
return ReadRawVarint64() != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string field from the stream.
|
||||
/// </summary>
|
||||
public string ReadString()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ReadString(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an embedded message field value from the stream.
|
||||
/// </summary>
|
||||
public void ReadMessage(IMessage builder)
|
||||
{
|
||||
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalMergeFrom method),
|
||||
// what we're doing here works fine, but could be more efficient.
|
||||
// What happends is that we first initialize a ParseContext from the current coded input stream only to parse the length of the message, at which point
|
||||
// we will need to switch back again to CodedInputStream-based parsing (which involves copying and storing the state) to be able to
|
||||
// invoke the legacy MergeFrom(CodedInputStream) method.
|
||||
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
ParseContext.Initialize(ref span, ref state, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
ParsingPrimitivesMessages.ReadMessage(ref ctx, builder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an embedded group field from the stream.
|
||||
/// </summary>
|
||||
public void ReadGroup(IMessage builder)
|
||||
{
|
||||
ParseContext.Initialize(this, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
ParsingPrimitivesMessages.ReadGroup(ref ctx, builder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bytes field value from the stream.
|
||||
/// </summary>
|
||||
public ByteString ReadBytes()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ReadBytes(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a uint32 field value from the stream.
|
||||
/// </summary>
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
return ReadRawVarint32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an enum field value from the stream.
|
||||
/// </summary>
|
||||
public int ReadEnum()
|
||||
{
|
||||
// Currently just a pass-through, but it's nice to separate it logically from WriteInt32.
|
||||
return (int) ReadRawVarint32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sfixed32 field value from the stream.
|
||||
/// </summary>
|
||||
public int ReadSFixed32()
|
||||
{
|
||||
return (int) ReadRawLittleEndian32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sfixed64 field value from the stream.
|
||||
/// </summary>
|
||||
public long ReadSFixed64()
|
||||
{
|
||||
return (long) ReadRawLittleEndian64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sint32 field value from the stream.
|
||||
/// </summary>
|
||||
public int ReadSInt32()
|
||||
{
|
||||
return ParsingPrimitives.DecodeZigZag32(ReadRawVarint32());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sint64 field value from the stream.
|
||||
/// </summary>
|
||||
public long ReadSInt64()
|
||||
{
|
||||
return ParsingPrimitives.DecodeZigZag64(ReadRawVarint64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length for length-delimited data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internally just reading a varint, but this method exists
|
||||
/// to make the calling code clearer.
|
||||
/// </remarks>
|
||||
public int ReadLength()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseLength(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
|
||||
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
|
||||
/// stream is left in the original position and the method returns <c>false</c>.
|
||||
/// </summary>
|
||||
public bool MaybeConsumeTag(uint tag)
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.MaybeConsumeTag(ref span, ref state, tag);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Underlying reading primitives
|
||||
|
||||
/// <summary>
|
||||
/// Reads a raw Varint from the stream. If larger than 32 bits, discard the upper bits.
|
||||
/// This method is optimised for the case where we've got lots of data in the buffer.
|
||||
/// That means we can check the size just once, then just read directly from the buffer
|
||||
/// without constant rechecking of the buffer length.
|
||||
/// </summary>
|
||||
internal uint ReadRawVarint32()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseRawVarint32(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a varint from the input one byte at a time, so that it does not
|
||||
/// read any bytes after the end of the varint. If you simply wrapped the
|
||||
/// stream in a CodedInputStream and used ReadRawVarint32(Stream)
|
||||
/// then you would probably end up reading past the end of the varint since
|
||||
/// CodedInputStream buffers its input.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
internal static uint ReadRawVarint32(Stream input)
|
||||
{
|
||||
return ParsingPrimitives.ReadRawVarint32(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a raw varint from the stream.
|
||||
/// </summary>
|
||||
internal ulong ReadRawVarint64()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseRawVarint64(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32-bit little-endian integer from the stream.
|
||||
/// </summary>
|
||||
internal uint ReadRawLittleEndian32()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseRawLittleEndian32(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64-bit little-endian integer from the stream.
|
||||
/// </summary>
|
||||
internal ulong ReadRawLittleEndian64()
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ParseRawLittleEndian64(ref span, ref state);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal reading and buffer management
|
||||
|
||||
/// <summary>
|
||||
/// Sets currentLimit to (current position) + byteLimit. This is called
|
||||
/// when descending into a length-delimited embedded message. The previous
|
||||
/// limit is returned.
|
||||
/// </summary>
|
||||
/// <returns>The old limit.</returns>
|
||||
internal int PushLimit(int byteLimit)
|
||||
{
|
||||
return SegmentedBufferHelper.PushLimit(ref state, byteLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards the current limit, returning the previous limit.
|
||||
/// </summary>
|
||||
internal void PopLimit(int oldLimit)
|
||||
{
|
||||
SegmentedBufferHelper.PopLimit(ref state, oldLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not all the data before the limit has been read.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool ReachedLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return SegmentedBufferHelper.IsReachedLimit(ref state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream has reached the end of the input. This is the
|
||||
/// case if either the end of the underlying input source has been reached or
|
||||
/// the stream has reached a limit created using PushLimit.
|
||||
/// </summary>
|
||||
public bool IsAtEnd
|
||||
{
|
||||
get
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return SegmentedBufferHelper.IsAtEnd(ref span, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when buffer is empty to read more bytes from the
|
||||
/// input. If <paramref name="mustSucceed"/> is true, RefillBuffer() guarantees that
|
||||
/// either there will be at least one byte in the buffer when it returns
|
||||
/// or it will throw an exception. If <paramref name="mustSucceed"/> is false,
|
||||
/// RefillBuffer() returns false if no more bytes were available.
|
||||
/// </summary>
|
||||
/// <param name="mustSucceed"></param>
|
||||
/// <returns></returns>
|
||||
private bool RefillBuffer(bool mustSucceed)
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return state.segmentedBufferHelper.RefillBuffer(ref span, ref state, mustSucceed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed size of bytes from the input.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">
|
||||
/// the end of the stream or the current limit was reached
|
||||
/// </exception>
|
||||
internal byte[] ReadRawBytes(int size)
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(buffer);
|
||||
return ParsingPrimitives.ReadRawBytes(ref span, ref state, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a top-level message or a nested message after the limits for this message have been pushed.
|
||||
/// (parser will proceed until the end of the current limit)
|
||||
/// NOTE: this method needs to be public because it's invoked by the generated code - e.g. msg.MergeFrom(CodedInputStream input) method
|
||||
/// </summary>
|
||||
public void ReadRawMessage(IMessage message)
|
||||
{
|
||||
ParseContext.Initialize(this, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
// This part of CodedOutputStream provides all the static entry points that are used
|
||||
// by generated code and internally to compute the size of messages prior to being
|
||||
// written to an instance of CodedOutputStream.
|
||||
public sealed partial class CodedOutputStream
|
||||
{
|
||||
private const int LittleEndian64Size = 8;
|
||||
private const int LittleEndian32Size = 4;
|
||||
|
||||
internal const int DoubleSize = LittleEndian64Size;
|
||||
internal const int FloatSize = LittleEndian32Size;
|
||||
internal const int BoolSize = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// double field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeDoubleSize(double value)
|
||||
{
|
||||
return DoubleSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// float field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeFloatSize(float value)
|
||||
{
|
||||
return FloatSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// uint64 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeUInt64Size(ulong value)
|
||||
{
|
||||
return ComputeRawVarint64Size(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// int64 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeInt64Size(long value)
|
||||
{
|
||||
return ComputeRawVarint64Size((ulong) value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// int32 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeInt32Size(int value)
|
||||
{
|
||||
if (value >= 0)
|
||||
{
|
||||
return ComputeRawVarint32Size((uint) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must sign-extend.
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// fixed64 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeFixed64Size(ulong value)
|
||||
{
|
||||
return LittleEndian64Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// fixed32 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeFixed32Size(uint value)
|
||||
{
|
||||
return LittleEndian32Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// bool field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeBoolSize(bool value)
|
||||
{
|
||||
return BoolSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// string field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeStringSize(String value)
|
||||
{
|
||||
int byteArraySize = WritingPrimitives.Utf8Encoding.GetByteCount(value);
|
||||
return ComputeLengthSize(byteArraySize) + byteArraySize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// group field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeGroupSize(IMessage value)
|
||||
{
|
||||
return value.CalculateSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// embedded message field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeMessageSize(IMessage value)
|
||||
{
|
||||
int size = value.CalculateSize();
|
||||
return ComputeLengthSize(size) + size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// bytes field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeBytesSize(ByteString value)
|
||||
{
|
||||
return ComputeLengthSize(value.Length) + value.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// uint32 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeUInt32Size(uint value)
|
||||
{
|
||||
return ComputeRawVarint32Size(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a
|
||||
/// enum field, including the tag. The caller is responsible for
|
||||
/// converting the enum value to its numeric value.
|
||||
/// </summary>
|
||||
public static int ComputeEnumSize(int value)
|
||||
{
|
||||
// Currently just a pass-through, but it's nice to separate it logically.
|
||||
return ComputeInt32Size(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// sfixed32 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeSFixed32Size(int value)
|
||||
{
|
||||
return LittleEndian32Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// sfixed64 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeSFixed64Size(long value)
|
||||
{
|
||||
return LittleEndian64Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// sint32 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeSInt32Size(int value)
|
||||
{
|
||||
return ComputeRawVarint32Size(WritingPrimitives.EncodeZigZag32(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode an
|
||||
/// sint64 field, including the tag.
|
||||
/// </summary>
|
||||
public static int ComputeSInt64Size(long value)
|
||||
{
|
||||
return ComputeRawVarint64Size(WritingPrimitives.EncodeZigZag64(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a length,
|
||||
/// as written by <see cref="WriteLength"/>.
|
||||
/// </summary>
|
||||
public static int ComputeLengthSize(int length)
|
||||
{
|
||||
return ComputeRawVarint32Size((uint) length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a varint.
|
||||
/// </summary>
|
||||
public static int ComputeRawVarint32Size(uint value)
|
||||
{
|
||||
if ((value & (0xffffffff << 7)) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if ((value & (0xffffffff << 14)) == 0)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
if ((value & (0xffffffff << 21)) == 0)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if ((value & (0xffffffff << 28)) == 0)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a varint.
|
||||
/// </summary>
|
||||
public static int ComputeRawVarint64Size(ulong value)
|
||||
{
|
||||
if ((value & (0xffffffffffffffffL << 7)) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 14)) == 0)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 21)) == 0)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 28)) == 0)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 35)) == 0)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 42)) == 0)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 49)) == 0)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 56)) == 0)
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
if ((value & (0xffffffffffffffffL << 63)) == 0)
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes that would be needed to encode a tag.
|
||||
/// </summary>
|
||||
public static int ComputeTagSize(int fieldNumber)
|
||||
{
|
||||
return ComputeRawVarint32Size(WireFormat.MakeTag(fieldNumber, 0));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,607 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Encodes and writes protocol message fields.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class is generally used by generated code to write appropriate
|
||||
/// primitives to the stream. It effectively encapsulates the lowest
|
||||
/// levels of protocol buffer format. Unlike some other implementations,
|
||||
/// this does not include combined "write tag and value" methods. Generated
|
||||
/// code knows the exact byte representations of the tags they're going to write,
|
||||
/// so there's no need to re-encode them each time. Manually-written code calling
|
||||
/// this class should just call one of the <c>WriteTag</c> overloads before each value.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Repeated fields and map fields are not handled by this class; use <c>RepeatedField<T></c>
|
||||
/// and <c>MapField<TKey, TValue></c> to serialize such fields.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
public sealed partial class CodedOutputStream : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The buffer size used by CreateInstance(Stream).
|
||||
/// </summary>
|
||||
public static readonly int DefaultBufferSize = 4096;
|
||||
|
||||
private readonly bool leaveOpen;
|
||||
private readonly byte[] buffer;
|
||||
private WriterInternalState state;
|
||||
|
||||
private readonly Stream output;
|
||||
|
||||
#region Construction
|
||||
/// <summary>
|
||||
/// Creates a new CodedOutputStream that writes directly to the given
|
||||
/// byte array. If more bytes are written than fit in the array,
|
||||
/// OutOfSpaceException will be thrown.
|
||||
/// </summary>
|
||||
public CodedOutputStream(byte[] flatArray) : this(flatArray, 0, flatArray.Length)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedOutputStream that writes directly to the given
|
||||
/// byte array slice. If more bytes are written than fit in the array,
|
||||
/// OutOfSpaceException will be thrown.
|
||||
/// </summary>
|
||||
private CodedOutputStream(byte[] buffer, int offset, int length)
|
||||
{
|
||||
this.output = null;
|
||||
this.buffer = ProtoPreconditions.CheckNotNull(buffer, nameof(buffer));
|
||||
this.state.position = offset;
|
||||
this.state.limit = offset + length;
|
||||
WriteBufferHelper.Initialize(this, out this.state.writeBufferHelper);
|
||||
leaveOpen = true; // Simple way of avoiding trying to dispose of a null reference
|
||||
}
|
||||
|
||||
private CodedOutputStream(Stream output, byte[] buffer, bool leaveOpen)
|
||||
{
|
||||
this.output = ProtoPreconditions.CheckNotNull(output, nameof(output));
|
||||
this.buffer = buffer;
|
||||
this.state.position = 0;
|
||||
this.state.limit = buffer.Length;
|
||||
WriteBufferHelper.Initialize(this, out this.state.writeBufferHelper);
|
||||
this.leaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CodedOutputStream" /> which write to the given stream, and disposes of that
|
||||
/// stream when the returned <c>CodedOutputStream</c> is disposed.
|
||||
/// </summary>
|
||||
/// <param name="output">The stream to write to. It will be disposed when the returned <c>CodedOutputStream is disposed.</c></param>
|
||||
public CodedOutputStream(Stream output) : this(output, DefaultBufferSize, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedOutputStream which write to the given stream and uses
|
||||
/// the specified buffer size.
|
||||
/// </summary>
|
||||
/// <param name="output">The stream to write to. It will be disposed when the returned <c>CodedOutputStream is disposed.</c></param>
|
||||
/// <param name="bufferSize">The size of buffer to use internally.</param>
|
||||
public CodedOutputStream(Stream output, int bufferSize) : this(output, new byte[bufferSize], false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedOutputStream which write to the given stream.
|
||||
/// </summary>
|
||||
/// <param name="output">The stream to write to.</param>
|
||||
/// <param name="leaveOpen">If <c>true</c>, <paramref name="output"/> is left open when the returned <c>CodedOutputStream</c> is disposed;
|
||||
/// if <c>false</c>, the provided stream is disposed as well.</param>
|
||||
public CodedOutputStream(Stream output, bool leaveOpen) : this(output, DefaultBufferSize, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CodedOutputStream which write to the given stream and uses
|
||||
/// the specified buffer size.
|
||||
/// </summary>
|
||||
/// <param name="output">The stream to write to.</param>
|
||||
/// <param name="bufferSize">The size of buffer to use internally.</param>
|
||||
/// <param name="leaveOpen">If <c>true</c>, <paramref name="output"/> is left open when the returned <c>CodedOutputStream</c> is disposed;
|
||||
/// if <c>false</c>, the provided stream is disposed as well.</param>
|
||||
public CodedOutputStream(Stream output, int bufferSize, bool leaveOpen) : this(output, new byte[bufferSize], leaveOpen)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current position in the stream, or the position in the output buffer
|
||||
/// </summary>
|
||||
public long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (output != null)
|
||||
{
|
||||
return output.Position + state.position;
|
||||
}
|
||||
return state.position;
|
||||
}
|
||||
}
|
||||
|
||||
#region Writing of values (not including tags)
|
||||
|
||||
/// <summary>
|
||||
/// Writes a double field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteDouble(double value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteDouble(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a float field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFloat(float value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteFloat(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a uint64 field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteUInt64(ulong value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteUInt64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int64 field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteInt64(long value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteInt64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int32 field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteInt32(int value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteInt32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a fixed64 field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFixed64(ulong value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteFixed64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a fixed32 field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFixed32(uint value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteFixed32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a bool field value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteBool(bool value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteBool(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string field value, without a tag, to the stream.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteString(string value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteString(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message, without a tag, to the stream.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteMessage(IMessage value)
|
||||
{
|
||||
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalWriteTo method),
|
||||
// what we're doing here works fine, but could be more efficient.
|
||||
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
|
||||
var span = new Span<byte>(buffer);
|
||||
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WritingPrimitivesMessages.WriteMessage(ref ctx, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message, without a tag, to the stream.
|
||||
/// Only the message data is written, without a length-delimiter.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteRawMessage(IMessage value)
|
||||
{
|
||||
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalWriteTo method),
|
||||
// what we're doing here works fine, but could be more efficient.
|
||||
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
|
||||
var span = new Span<byte>(buffer);
|
||||
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WritingPrimitivesMessages.WriteRawMessage(ref ctx, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a group, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteGroup(IMessage value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WritingPrimitivesMessages.WriteGroup(ref ctx, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a byte string, without a tag, to the stream.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteBytes(ByteString value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteBytes(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a uint32 value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteUInt32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an enum value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteEnum(int value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteEnum(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sfixed32 value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public void WriteSFixed32(int value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteSFixed32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sfixed64 value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSFixed64(long value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteSFixed64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sint32 value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSInt32(int value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteSInt32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sint64 value, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSInt64(long value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteSInt64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a length (in bytes) for length-delimited data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method simply writes a rawint, but exists for clarity in calling code.
|
||||
/// </remarks>
|
||||
/// <param name="length">Length value, in bytes.</param>
|
||||
public void WriteLength(int length)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteLength(ref span, ref state, length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Raw tag writing
|
||||
/// <summary>
|
||||
/// Encodes and writes a tag.
|
||||
/// </summary>
|
||||
/// <param name="fieldNumber">The number of the field to write the tag for</param>
|
||||
/// <param name="type">The wire format type of the tag to write</param>
|
||||
public void WriteTag(int fieldNumber, WireFormat.WireType type)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteTag(ref span, ref state, fieldNumber, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an already-encoded tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The encoded tag</param>
|
||||
public void WriteTag(uint tag)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteTag(ref span, ref state, tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given single-byte tag directly to the stream.
|
||||
/// </summary>
|
||||
/// <param name="b1">The encoded tag</param>
|
||||
public void WriteRawTag(byte b1)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawTag(ref span, ref state, b1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given two-byte tag directly to the stream.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given three-byte tag directly to the stream.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given four-byte tag directly to the stream.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
/// <param name="b4">The fourth byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3, b4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given five-byte tag directly to the stream.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
/// <param name="b4">The fourth byte of the encoded tag</param>
|
||||
/// <param name="b5">The fifth byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3, b4, b5);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Underlying writing primitives
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 32 bit value as a varint. The fast route is taken when
|
||||
/// there's enough buffer space left to whizz through without checking
|
||||
/// for each byte; otherwise, we resort to calling WriteRawByte each time.
|
||||
/// </summary>
|
||||
internal void WriteRawVarint32(uint value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawVarint32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
internal void WriteRawVarint64(ulong value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawVarint64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
internal void WriteRawLittleEndian32(uint value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawLittleEndian32(ref span, ref state, value);
|
||||
}
|
||||
|
||||
internal void WriteRawLittleEndian64(ulong value)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawLittleEndian64(ref span, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes out an array of bytes.
|
||||
/// </summary>
|
||||
internal void WriteRawBytes(byte[] value)
|
||||
{
|
||||
WriteRawBytes(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes out part of an array of bytes.
|
||||
/// </summary>
|
||||
internal void WriteRawBytes(byte[] value, int offset, int length)
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WritingPrimitives.WriteRawBytes(ref span, ref state, value, offset, length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a CodedOutputStream wrapping a flat byte array
|
||||
/// ran out of space.
|
||||
/// </summary>
|
||||
public sealed class OutOfSpaceException : IOException
|
||||
{
|
||||
internal OutOfSpaceException()
|
||||
: base("CodedOutputStream was writing to a flat byte array and ran out of space.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any buffered data and optionally closes the underlying stream, if any.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, any underlying stream is closed by this method. To configure this behaviour,
|
||||
/// use a constructor overload with a <c>leaveOpen</c> parameter. If this instance does not
|
||||
/// have an underlying stream, this method does nothing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For the sake of efficiency, calling this method does not prevent future write calls - but
|
||||
/// if a later write ends up writing to a stream which has been disposed, that is likely to
|
||||
/// fail. It is recommend that you not call any other methods after this.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
Flush();
|
||||
if (!leaveOpen)
|
||||
{
|
||||
output.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any buffered data to the underlying stream (if there is one).
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
var span = new Span<byte>(buffer);
|
||||
WriteBufferHelper.Flush(ref span, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that SpaceLeft returns zero. It's common to create a byte array
|
||||
/// that is exactly big enough to hold a message, then write to it with
|
||||
/// a CodedOutputStream. Calling CheckNoSpaceLeft after writing verifies that
|
||||
/// the message was actually as big as expected, which can help finding bugs.
|
||||
/// </summary>
|
||||
public void CheckNoSpaceLeft()
|
||||
{
|
||||
WriteBufferHelper.CheckNoSpaceLeft(ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If writing to a flat array, returns the space left in the array. Otherwise,
|
||||
/// throws an InvalidOperationException.
|
||||
/// </summary>
|
||||
public int SpaceLeft => WriteBufferHelper.GetSpaceLeft(ref state);
|
||||
|
||||
internal byte[] InternalBuffer => buffer;
|
||||
|
||||
internal Stream InternalOutputStream => output;
|
||||
|
||||
internal ref WriterInternalState InternalState => ref state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 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
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LC.Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to compare if two Lists are the same, and the hash code
|
||||
/// of a List.
|
||||
/// </summary>
|
||||
public static class Lists
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if two lists are equal.
|
||||
/// </summary>
|
||||
public static bool Equals<T>(List<T> left, List<T> right)
|
||||
{
|
||||
if (left == right)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (left == null || right == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (left.Count != right.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
|
||||
for (int i = 0; i < left.Count; i++)
|
||||
{
|
||||
if (!comparer.Equals(left[i], right[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list's hash code.
|
||||
/// </summary>
|
||||
public static int GetHashCode<T>(List<T> list)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int hash = 31;
|
||||
foreach (T element in list)
|
||||
{
|
||||
hash = hash * 29 + element.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,762 @@
|
|||
#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
|
||||
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Representation of a map field in a Protocol Buffer message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
|
||||
/// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Null values are not permitted in the map, either for wrapper types or regular messages.
|
||||
/// If a map is deserialized from a data stream and the value is missing from an entry, a default value
|
||||
/// is created instead. For primitive types, that is the regular default value (0, the empty string and so
|
||||
/// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
|
||||
/// encoded value for the field.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This implementation does not generally prohibit the use of key/value types which are not
|
||||
/// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
|
||||
/// that all operations will work in such cases.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The order in which entries are returned when iterating over this object is undefined, and may change
|
||||
/// in future versions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
|
||||
#if !NET35
|
||||
, IReadOnlyDictionary<TKey, TValue>
|
||||
#endif
|
||||
{
|
||||
private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
|
||||
private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
|
||||
|
||||
// TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
|
||||
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
|
||||
new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
|
||||
private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of this object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A deep clone of this object.
|
||||
/// </returns>
|
||||
public MapField<TKey, TValue> Clone()
|
||||
{
|
||||
var clone = new MapField<TKey, TValue>();
|
||||
// Keys are never cloneable. Values might be.
|
||||
if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
|
||||
{
|
||||
foreach (var pair in list)
|
||||
{
|
||||
clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing is cloneable, so we don't need to worry.
|
||||
clone.Add(this);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key/value pair to the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
|
||||
/// </remarks>
|
||||
/// <param name="key">The key to add</param>
|
||||
/// <param name="value">The value to add.</param>
|
||||
/// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
// Validation of arguments happens in ContainsKey and the indexer
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException("Key already exists in map", nameof(key));
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified key is present in the map.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to check.</param>
|
||||
/// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
return map.ContainsKey(key);
|
||||
}
|
||||
|
||||
private bool ContainsValue(TValue value) =>
|
||||
list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
|
||||
|
||||
/// <summary>
|
||||
/// Removes the entry identified by the given key from the map.
|
||||
/// </summary>
|
||||
/// <param name="key">The key indicating the entry to remove from the map.</param>
|
||||
/// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
map.Remove(key);
|
||||
node.List.Remove(node);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key whose value to get.</param>
|
||||
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found;
|
||||
/// otherwise, the default value for the type of the <paramref name="value"/> parameter.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
value = node.Value.Value;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(TValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found,
|
||||
/// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
TValue value;
|
||||
if (TryGetValue(key, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
set
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
// value == null check here is redundant, but avoids boxing.
|
||||
if (value == null)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
|
||||
}
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
var pair = new KeyValuePair<TKey, TValue>(key, value);
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
node.Value = pair;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = list.AddLast(pair);
|
||||
map[key] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the keys in the map.
|
||||
/// </summary>
|
||||
public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the values in the map.
|
||||
/// </summary>
|
||||
public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified entries to the map. The keys and values are not automatically cloned.
|
||||
/// </summary>
|
||||
/// <param name="entries">The entries to add to the map.</param>
|
||||
public void Add(IDictionary<TKey, TValue> entries)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(entries, nameof(entries));
|
||||
foreach (var pair in entries)
|
||||
{
|
||||
Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through a collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the map.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add to the map.</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the map.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
map.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether map contains an entry equivalent to the given key/value pair.
|
||||
/// </summary>
|
||||
/// <param name="item">The key/value pair to find.</param>
|
||||
/// <returns></returns>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
TValue value;
|
||||
return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the key/value pairs in this map to an array.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy the entries into.</param>
|
||||
/// <param name="arrayIndex">The index of the array at which to start copying values.</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key/value pair from the map.
|
||||
/// </summary>
|
||||
/// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
|
||||
/// <param name="item">The key/value pair to remove.</param>
|
||||
/// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
if (item.Key == null)
|
||||
{
|
||||
throw new ArgumentException("Key is null", nameof(item));
|
||||
}
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(item.Key, out node) &&
|
||||
EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
|
||||
{
|
||||
map.Remove(item.Key);
|
||||
node.List.Remove(node);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the map.
|
||||
/// </summary>
|
||||
public int Count { get { return list.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the map is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return Equals(other as MapField<TKey, TValue>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var keyComparer = KeyEqualityComparer;
|
||||
var valueComparer = ValueEqualityComparer;
|
||||
int hash = 0;
|
||||
foreach (var pair in list)
|
||||
{
|
||||
hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this map with another for equality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The order of the key/value pairs in the maps is not deemed significant in this comparison.
|
||||
/// </remarks>
|
||||
/// <param name="other">The map to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(MapField<TKey, TValue> other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (other == this)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (other.Count != this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var valueComparer = ValueEqualityComparer;
|
||||
foreach (var pair in this)
|
||||
{
|
||||
TValue value;
|
||||
if (!other.TryGetValue(pair.Key, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!valueComparer.Equals(value, pair.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds entries to the map from the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is assumed that the stream is initially positioned after the tag specified by the codec.
|
||||
/// This method will continue reading entries from the stream until the end is reached, or
|
||||
/// a different tag is encountered.
|
||||
/// </remarks>
|
||||
/// <param name="input">Stream to read from</param>
|
||||
/// <param name="codec">Codec describing how the key/value pairs are encoded</param>
|
||||
public void AddEntriesFrom(CodedInputStream input, Codec codec)
|
||||
{
|
||||
ParseContext.Initialize(input, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
AddEntriesFrom(ref ctx, codec);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds entries to the map from the given parse context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is assumed that the input is initially positioned after the tag specified by the codec.
|
||||
/// This method will continue reading entries from the input until the end is reached, or
|
||||
/// a different tag is encountered.
|
||||
/// </remarks>
|
||||
/// <param name="ctx">Input to read from</param>
|
||||
/// <param name="codec">Codec describing how the key/value pairs are encoded</param>
|
||||
[SecuritySafeCritical]
|
||||
public void AddEntriesFrom(ref ParseContext ctx, Codec codec)
|
||||
{
|
||||
do
|
||||
{
|
||||
KeyValuePair<TKey, TValue> entry = ParsingPrimitivesMessages.ReadMapEntry(ref ctx, codec);
|
||||
this[entry.Key] = entry.Value;
|
||||
} while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, codec.MapTag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this map to the given coded output stream, using the specified codec
|
||||
/// to encode each entry.
|
||||
/// </summary>
|
||||
/// <param name="output">The output stream to write to.</param>
|
||||
/// <param name="codec">The codec to use for each entry.</param>
|
||||
public void WriteTo(CodedOutputStream output, Codec codec)
|
||||
{
|
||||
WriteContext.Initialize(output, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WriteTo(ref ctx, codec);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this map to the given write context, using the specified codec
|
||||
/// to encode each entry.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The write context to write to.</param>
|
||||
/// <param name="codec">The codec to use for each entry.</param>
|
||||
[SecuritySafeCritical]
|
||||
public void WriteTo(ref WriteContext ctx, Codec codec)
|
||||
{
|
||||
foreach (var entry in list)
|
||||
{
|
||||
ctx.WriteTag(codec.MapTag);
|
||||
|
||||
WritingPrimitives.WriteLength(ref ctx.buffer, ref ctx.state, CalculateEntrySize(codec, entry));
|
||||
codec.KeyCodec.WriteTagAndValue(ref ctx, entry.Key);
|
||||
codec.ValueCodec.WriteTagAndValue(ref ctx, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this map based on the given entry codec.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to use to encode each entry.</param>
|
||||
/// <returns></returns>
|
||||
public int CalculateSize(Codec codec)
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int size = 0;
|
||||
foreach (var entry in list)
|
||||
{
|
||||
int entrySize = CalculateEntrySize(codec, entry);
|
||||
|
||||
size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
|
||||
size += CodedOutputStream.ComputeLengthSize(entrySize) + entrySize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private static int CalculateEntrySize(Codec codec, KeyValuePair<TKey, TValue> entry)
|
||||
{
|
||||
return codec.KeyCodec.CalculateSizeWithTag(entry.Key) + codec.ValueCodec.CalculateSizeWithTag(entry.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this repeated field, in the same
|
||||
/// way as it would be represented by the default JSON formatter.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
JsonFormatter.Default.WriteDictionary(writer, this);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
#region IDictionary explicit interface implementation
|
||||
void IDictionary.Add(object key, object value)
|
||||
{
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ContainsKey((TKey)key);
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return new DictionaryEnumerator(GetEnumerator());
|
||||
}
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(key, nameof(key));
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Remove((TKey)key);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
// This is ugly and slow as heck, but with any luck it will never be used anyway.
|
||||
ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
|
||||
temp.CopyTo(array, index);
|
||||
}
|
||||
|
||||
bool IDictionary.IsFixedSize { get { return false; } }
|
||||
|
||||
ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
|
||||
|
||||
ICollection IDictionary.Values { get { return (ICollection)Values; } }
|
||||
|
||||
bool ICollection.IsSynchronized { get { return false; } }
|
||||
|
||||
object ICollection.SyncRoot { get { return this; } }
|
||||
|
||||
object IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(key, nameof(key));
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
TValue value;
|
||||
TryGetValue((TKey)key, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this[(TKey)key] = (TValue)value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IReadOnlyDictionary explicit interface implementation
|
||||
#if !NET35
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
private class DictionaryEnumerator : IDictionaryEnumerator
|
||||
{
|
||||
private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
|
||||
|
||||
internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
|
||||
{
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return enumerator.MoveNext();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
enumerator.Reset();
|
||||
}
|
||||
|
||||
public object Current { get { return Entry; } }
|
||||
public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
|
||||
public object Key { get { return enumerator.Current.Key; } }
|
||||
public object Value { get { return enumerator.Current.Value; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A codec for a specific map field. This contains all the information required to encode and
|
||||
/// decode the nested messages.
|
||||
/// </summary>
|
||||
public sealed class Codec
|
||||
{
|
||||
private readonly FieldCodec<TKey> keyCodec;
|
||||
private readonly FieldCodec<TValue> valueCodec;
|
||||
private readonly uint mapTag;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new entry codec based on a separate key codec and value codec,
|
||||
/// and the tag to use for each map entry.
|
||||
/// </summary>
|
||||
/// <param name="keyCodec">The key codec.</param>
|
||||
/// <param name="valueCodec">The value codec.</param>
|
||||
/// <param name="mapTag">The map tag to use to introduce each map entry.</param>
|
||||
public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
|
||||
{
|
||||
this.keyCodec = keyCodec;
|
||||
this.valueCodec = valueCodec;
|
||||
this.mapTag = mapTag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key codec.
|
||||
/// </summary>
|
||||
internal FieldCodec<TKey> KeyCodec => keyCodec;
|
||||
|
||||
/// <summary>
|
||||
/// The value codec.
|
||||
/// </summary>
|
||||
internal FieldCodec<TValue> ValueCodec => valueCodec;
|
||||
|
||||
/// <summary>
|
||||
/// The tag used in the enclosing message to indicate map entries.
|
||||
/// </summary>
|
||||
internal uint MapTag => mapTag;
|
||||
}
|
||||
|
||||
private class MapView<T> : ICollection<T>, ICollection
|
||||
{
|
||||
private readonly MapField<TKey, TValue> parent;
|
||||
private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
|
||||
private readonly Func<T, bool> containsCheck;
|
||||
|
||||
internal MapView(
|
||||
MapField<TKey, TValue> parent,
|
||||
Func<KeyValuePair<TKey, TValue>, T> projection,
|
||||
Func<T, bool> containsCheck)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.projection = projection;
|
||||
this.containsCheck = containsCheck;
|
||||
}
|
||||
|
||||
public int Count { get { return parent.Count; } }
|
||||
|
||||
public bool IsReadOnly { get { return true; } }
|
||||
|
||||
public bool IsSynchronized { get { return false; } }
|
||||
|
||||
public object SyncRoot { get { return parent; } }
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return containsCheck(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
if (arrayIndex + Count > array.Length)
|
||||
{
|
||||
throw new ArgumentException("Not enough space in the array", nameof(array));
|
||||
}
|
||||
foreach (var item in this)
|
||||
{
|
||||
array[arrayIndex++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return parent.list.Select(projection).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
if (index + Count > array.Length)
|
||||
{
|
||||
throw new ArgumentException("Not enough space in the array", nameof(array));
|
||||
}
|
||||
foreach (var item in this)
|
||||
{
|
||||
array.SetValue(item, index++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LC.Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a central place to implement equality comparisons, primarily for bitwise float/double equality.
|
||||
/// </summary>
|
||||
public static class ProtobufEqualityComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an equality comparer for <typeparamref name="T"/> suitable for Protobuf equality comparisons.
|
||||
/// This is usually just the default equality comparer for the type, but floating point numbers are compared
|
||||
/// bitwise.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of equality comparer to return.</typeparam>
|
||||
/// <returns>The equality comparer.</returns>
|
||||
public static EqualityComparer<T> GetEqualityComparer<T>()
|
||||
{
|
||||
return typeof(T) == typeof(double) ? (EqualityComparer<T>) (object) BitwiseDoubleEqualityComparer
|
||||
: typeof(T) == typeof(float) ? (EqualityComparer<T>) (object) BitwiseSingleEqualityComparer
|
||||
: typeof(T) == typeof(double?) ? (EqualityComparer<T>) (object) BitwiseNullableDoubleEqualityComparer
|
||||
: typeof(T) == typeof(float?) ? (EqualityComparer<T>) (object) BitwiseNullableSingleEqualityComparer
|
||||
: EqualityComparer<T>.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an equality comparer suitable for comparing 64-bit floating point values, by bitwise comparison.
|
||||
/// (NaN values are considered equal, but only when they have the same representation.)
|
||||
/// </summary>
|
||||
public static EqualityComparer<double> BitwiseDoubleEqualityComparer { get; } = new BitwiseDoubleEqualityComparerImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an equality comparer suitable for comparing 32-bit floating point values, by bitwise comparison.
|
||||
/// (NaN values are considered equal, but only when they have the same representation.)
|
||||
/// </summary>
|
||||
public static EqualityComparer<float> BitwiseSingleEqualityComparer { get; } = new BitwiseSingleEqualityComparerImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an equality comparer suitable for comparing nullable 64-bit floating point values, by bitwise comparison.
|
||||
/// (NaN values are considered equal, but only when they have the same representation.)
|
||||
/// </summary>
|
||||
public static EqualityComparer<double?> BitwiseNullableDoubleEqualityComparer { get; } = new BitwiseNullableDoubleEqualityComparerImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an equality comparer suitable for comparing nullable 32-bit floating point values, by bitwise comparison.
|
||||
/// (NaN values are considered equal, but only when they have the same representation.)
|
||||
/// </summary>
|
||||
public static EqualityComparer<float?> BitwiseNullableSingleEqualityComparer { get; } = new BitwiseNullableSingleEqualityComparerImpl();
|
||||
|
||||
private class BitwiseDoubleEqualityComparerImpl : EqualityComparer<double>
|
||||
{
|
||||
public override bool Equals(double x, double y) =>
|
||||
BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
|
||||
|
||||
public override int GetHashCode(double obj) =>
|
||||
BitConverter.DoubleToInt64Bits(obj).GetHashCode();
|
||||
}
|
||||
|
||||
private class BitwiseSingleEqualityComparerImpl : EqualityComparer<float>
|
||||
{
|
||||
// Just promote values to double and use BitConverter.DoubleToInt64Bits,
|
||||
// as there's no BitConverter.SingleToInt32Bits, unfortunately.
|
||||
|
||||
public override bool Equals(float x, float y) =>
|
||||
BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
|
||||
|
||||
public override int GetHashCode(float obj) =>
|
||||
BitConverter.DoubleToInt64Bits(obj).GetHashCode();
|
||||
}
|
||||
|
||||
private class BitwiseNullableDoubleEqualityComparerImpl : EqualityComparer<double?>
|
||||
{
|
||||
public override bool Equals(double? x, double? y) =>
|
||||
x == null && y == null ? true
|
||||
: x == null || y == null ? false
|
||||
: BitwiseDoubleEqualityComparer.Equals(x.Value, y.Value);
|
||||
|
||||
// The hash code for null is just a constant which is at least *unlikely* to be used
|
||||
// elsewhere. (Compared with 0, say.)
|
||||
public override int GetHashCode(double? obj) =>
|
||||
obj == null ? 293864 : BitwiseDoubleEqualityComparer.GetHashCode(obj.Value);
|
||||
}
|
||||
|
||||
private class BitwiseNullableSingleEqualityComparerImpl : EqualityComparer<float?>
|
||||
{
|
||||
public override bool Equals(float? x, float? y) =>
|
||||
x == null && y == null ? true
|
||||
: x == null || y == null ? false
|
||||
: BitwiseSingleEqualityComparer.Equals(x.Value, y.Value);
|
||||
|
||||
// The hash code for null is just a constant which is at least *unlikely* to be used
|
||||
// elsewhere. (Compared with 0, say.)
|
||||
public override int GetHashCode(float? obj) =>
|
||||
obj == null ? 293864 : BitwiseSingleEqualityComparer.GetHashCode(obj.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LC.Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only wrapper around another dictionary.
|
||||
/// </summary>
|
||||
internal sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly IDictionary<TKey, TValue> wrapped;
|
||||
|
||||
public ReadOnlyDictionary(IDictionary<TKey, TValue> wrapped)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return wrapped.ContainsKey(key);
|
||||
}
|
||||
|
||||
public ICollection<TKey> Keys
|
||||
{
|
||||
get { return wrapped.Keys; }
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return wrapped.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public ICollection<TValue> Values
|
||||
{
|
||||
get { return wrapped.Values; }
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get { return wrapped[key]; }
|
||||
set { throw new InvalidOperationException(); }
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return wrapped.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
wrapped.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return wrapped.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return wrapped.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable) wrapped).GetEnumerator();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return wrapped.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return wrapped.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return wrapped.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,698 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
|
||||
namespace LC.Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// The contents of a repeated field: essentially, a collection with some extra
|
||||
/// restrictions (no null values) and capabilities (deep cloning).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation does not generally prohibit the use of types which are not
|
||||
/// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The element type of the repeated field.</typeparam>
|
||||
public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>
|
||||
#if !NET35
|
||||
, IReadOnlyList<T>
|
||||
#endif
|
||||
{
|
||||
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
|
||||
private static readonly T[] EmptyArray = new T[0];
|
||||
private const int MinArraySize = 8;
|
||||
|
||||
private T[] array = EmptyArray;
|
||||
private int count = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of this repeated field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the field type is
|
||||
/// a message type, each element is also cloned; otherwise, it is
|
||||
/// assumed that the field type is primitive (including string and
|
||||
/// bytes, both of which are immutable) and so a simple copy is
|
||||
/// equivalent to a deep clone.
|
||||
/// </remarks>
|
||||
/// <returns>A deep clone of this repeated field.</returns>
|
||||
public RepeatedField<T> Clone()
|
||||
{
|
||||
RepeatedField<T> clone = new RepeatedField<T>();
|
||||
if (array != EmptyArray)
|
||||
{
|
||||
clone.array = (T[])array.Clone();
|
||||
IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
|
||||
if (cloneableArray != null)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
clone.array[i] = cloneableArray[i].Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
clone.count = count;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the entries from the given input stream, decoding them with the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="input">The input stream to read from.</param>
|
||||
/// <param name="codec">The codec to use in order to read each entry.</param>
|
||||
public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
|
||||
{
|
||||
ParseContext.Initialize(input, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
AddEntriesFrom(ref ctx, codec);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the entries from the given parse context, decoding them with the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The input to read from.</param>
|
||||
/// <param name="codec">The codec to use in order to read each entry.</param>
|
||||
[SecuritySafeCritical]
|
||||
public void AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)
|
||||
{
|
||||
// TODO: Inline some of the Add code, so we can avoid checking the size on every
|
||||
// iteration.
|
||||
uint tag = ctx.state.lastTag;
|
||||
var reader = codec.ValueReader;
|
||||
// Non-nullable value types can be packed or not.
|
||||
if (FieldCodec<T>.IsPackedRepeatedField(tag))
|
||||
{
|
||||
int length = ctx.ReadLength();
|
||||
if (length > 0)
|
||||
{
|
||||
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
|
||||
|
||||
// If the content is fixed size then we can calculate the length
|
||||
// of the repeated field and pre-initialize the underlying collection.
|
||||
//
|
||||
// Check that the supplied length doesn't exceed the underlying buffer.
|
||||
// That prevents a malicious length from initializing a very large collection.
|
||||
if (codec.FixedSize > 0 && length % codec.FixedSize == 0 && ParsingPrimitives.IsDataAvailable(ref ctx.state, length))
|
||||
{
|
||||
EnsureSize(count + (length / codec.FixedSize));
|
||||
|
||||
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
|
||||
{
|
||||
// Only FieldCodecs with a fixed size can reach here, and they are all known
|
||||
// types that don't allow the user to specify a custom reader action.
|
||||
// reader action will never return null.
|
||||
array[count++] = reader(ref ctx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Content is variable size so add until we reach the limit.
|
||||
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
|
||||
{
|
||||
Add(reader(ref ctx));
|
||||
}
|
||||
}
|
||||
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
|
||||
}
|
||||
// Empty packed field. Odd, but valid - just ignore.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not packed... (possibly not packable)
|
||||
do
|
||||
{
|
||||
Add(reader(ref ctx));
|
||||
} while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, tag));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this collection based on the given codec.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to use when encoding each field.</param>
|
||||
/// <returns>The number of bytes that would be written to an output by one of the <c>WriteTo</c> methods,
|
||||
/// using the same codec.</returns>
|
||||
public int CalculateSize(FieldCodec<T> codec)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint tag = codec.Tag;
|
||||
if (codec.PackedRepeatedField)
|
||||
{
|
||||
int dataSize = CalculatePackedDataSize(codec);
|
||||
return CodedOutputStream.ComputeRawVarint32Size(tag) +
|
||||
CodedOutputStream.ComputeLengthSize(dataSize) +
|
||||
dataSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sizeCalculator = codec.ValueSizeCalculator;
|
||||
int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
|
||||
if (codec.EndTag != 0)
|
||||
{
|
||||
size += count * CodedOutputStream.ComputeRawVarint32Size(codec.EndTag);
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
size += sizeCalculator(array[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculatePackedDataSize(FieldCodec<T> codec)
|
||||
{
|
||||
int fixedSize = codec.FixedSize;
|
||||
if (fixedSize == 0)
|
||||
{
|
||||
var calculator = codec.ValueSizeCalculator;
|
||||
int tmp = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tmp += calculator(array[i]);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fixedSize * Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
|
||||
/// encoding each value using the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="output">The output stream to write to.</param>
|
||||
/// <param name="codec">The codec to use when encoding each value.</param>
|
||||
public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
|
||||
{
|
||||
WriteContext.Initialize(output, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WriteTo(ref ctx, codec);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this collection to the given write context,
|
||||
/// encoding each value using the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The write context to write to.</param>
|
||||
/// <param name="codec">The codec to use when encoding each value.</param>
|
||||
[SecuritySafeCritical]
|
||||
public void WriteTo(ref WriteContext ctx, FieldCodec<T> codec)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var writer = codec.ValueWriter;
|
||||
var tag = codec.Tag;
|
||||
if (codec.PackedRepeatedField)
|
||||
{
|
||||
// Packed primitive type
|
||||
int size = CalculatePackedDataSize(codec);
|
||||
ctx.WriteTag(tag);
|
||||
ctx.WriteLength(size);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
writer(ref ctx, array[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not packed: a simple tag/value pair for each value.
|
||||
// Can't use codec.WriteTagAndValue, as that omits default values.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ctx.WriteTag(tag);
|
||||
writer(ref ctx, array[i]);
|
||||
if (codec.EndTag != 0)
|
||||
{
|
||||
ctx.WriteTag(codec.EndTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the capacity of the RepeatedField's internal array. WHen set, the internal array is reallocated to the given capacity.
|
||||
/// <exception cref="ArgumentOutOfRangeException">The new value is less than Count -or- when Count is less than 0.</exception>
|
||||
/// </summary>
|
||||
public int Capacity
|
||||
{
|
||||
get { return array.Length; }
|
||||
set
|
||||
{
|
||||
if (value < count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Capacity", value,
|
||||
$"Cannot set Capacity to a value smaller than the current item count, {count}");
|
||||
}
|
||||
|
||||
if (value >= 0 && value != array.Length)
|
||||
{
|
||||
SetSize(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// May increase the size of the internal array, but will never shrink it.
|
||||
private void EnsureSize(int size)
|
||||
{
|
||||
if (array.Length < size)
|
||||
{
|
||||
size = Math.Max(size, MinArraySize);
|
||||
int newSize = Math.Max(array.Length * 2, size);
|
||||
SetSize(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the internal array to an exact size.
|
||||
private void SetSize(int size)
|
||||
{
|
||||
if (size != array.Length)
|
||||
{
|
||||
var tmp = new T[size];
|
||||
Array.Copy(array, 0, tmp, 0, count);
|
||||
array = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
EnsureSize(count + 1);
|
||||
array[count++] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the collection.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
array = EmptyArray;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this collection contains the given item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to find.</param>
|
||||
/// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return IndexOf(item) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies this collection to the given array.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy to.</param>
|
||||
/// <param name="arrayIndex">The first index of the array to copy to.</param>
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
Array.Copy(this.array, 0, array, arrayIndex, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified item from the collection
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
if (index == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Array.Copy(array, index + 1, array, index, count - index - 1);
|
||||
count--;
|
||||
array[count] = default(T);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the collection.
|
||||
/// </summary>
|
||||
public int Count => count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the collection is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the specified values into this collection.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to add to this collection.</param>
|
||||
public void AddRange(IEnumerable<T> values)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(values, nameof(values));
|
||||
|
||||
// Optimization 1: If the collection we're adding is already a RepeatedField<T>,
|
||||
// we know the values are valid.
|
||||
var otherRepeatedField = values as RepeatedField<T>;
|
||||
if (otherRepeatedField != null)
|
||||
{
|
||||
EnsureSize(count + otherRepeatedField.count);
|
||||
Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
|
||||
count += otherRepeatedField.count;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization 2: The collection is an ICollection, so we can expand
|
||||
// just once and ask the collection to copy itself into the array.
|
||||
var collection = values as ICollection;
|
||||
if (collection != null)
|
||||
{
|
||||
var extraCount = collection.Count;
|
||||
// For reference types and nullable value types, we need to check that there are no nulls
|
||||
// present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
|
||||
// We expect the JITter to optimize this test to true/false, so it's effectively conditional
|
||||
// specialization.
|
||||
if (default(T) == null)
|
||||
{
|
||||
// TODO: Measure whether iterating once to check and then letting the collection copy
|
||||
// itself is faster or slower than iterating and adding as we go. For large
|
||||
// collections this will not be great in terms of cache usage... but the optimized
|
||||
// copy may be significantly faster than doing it one at a time.
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentException("Sequence contained null element", nameof(values));
|
||||
}
|
||||
}
|
||||
}
|
||||
EnsureSize(count + extraCount);
|
||||
collection.CopyTo(array, count);
|
||||
count += extraCount;
|
||||
return;
|
||||
}
|
||||
|
||||
// We *could* check for ICollection<T> as well, but very very few collections implement
|
||||
// ICollection<T> but not ICollection. (HashSet<T> does, for one...)
|
||||
|
||||
// Fall back to a slower path of adding items one at a time.
|
||||
foreach (T item in values)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the specified values into this collection. This method is present to
|
||||
/// allow repeated fields to be constructed from queries within collection initializers.
|
||||
/// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
|
||||
/// method instead for clarity.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to add to this collection.</param>
|
||||
public void Add(IEnumerable<T> values)
|
||||
{
|
||||
AddRange(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
yield return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as RepeatedField<T>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through a collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
hash = hash * 31 + array[i].GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this repeated field with another for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">The repeated field to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(RepeatedField<T> other)
|
||||
{
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (other.Count != this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
EqualityComparer<T> comparer = EqualityComparer;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!comparer.Equals(array[i], other.array[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the given item within the collection, or -1 if the item is not
|
||||
/// present.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to find in the collection.</param>
|
||||
/// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
EqualityComparer<T> comparer = EqualityComparer;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (comparer.Equals(array[i], item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the given item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index at which to insert the item.</param>
|
||||
/// <param name="item">The item to insert.</param>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
if (index < 0 || index > count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
EnsureSize(count + 1);
|
||||
Array.Copy(array, index, array, index + 1, count - index);
|
||||
array[index] = item;
|
||||
count++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
Array.Copy(array, index + 1, array, index, count - index - 1);
|
||||
count--;
|
||||
array[count] = default(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this repeated field, in the same
|
||||
/// way as it would be represented by the default JSON formatter.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
JsonFormatter.Default.WriteList(writer, this);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item at the specified index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The element at the specified index.
|
||||
/// </value>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The item at the specified index.</returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
|
||||
array[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Explicit interface implementation for IList and ICollection.
|
||||
bool IList.IsFixedSize => false;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
Array.Copy(this.array, 0, array, index, count);
|
||||
}
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get { return this[index]; }
|
||||
set { this[index] = (T)value; }
|
||||
}
|
||||
|
||||
int IList.Add(object value)
|
||||
{
|
||||
Add((T) value);
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
bool IList.Contains(object value)
|
||||
{
|
||||
return (value is T && Contains((T)value));
|
||||
}
|
||||
|
||||
int IList.IndexOf(object value)
|
||||
{
|
||||
if (!(value is T))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return IndexOf((T)value);
|
||||
}
|
||||
|
||||
void IList.Insert(int index, object value)
|
||||
{
|
||||
Insert(index, (T) value);
|
||||
}
|
||||
|
||||
void IList.Remove(object value)
|
||||
{
|
||||
if (!(value is T))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Remove((T)value);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 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
|
||||
|
||||
#if NET35
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Compatibility
|
||||
{
|
||||
// .NET Core (at least netstandard1.0) doesn't have Delegate.CreateDelegate, and .NET 3.5 doesn't have
|
||||
// MethodInfo.CreateDelegate. Proxy from one to the other on .NET 3.5...
|
||||
internal static class MethodInfoExtensions
|
||||
{
|
||||
internal static Delegate CreateDelegate(this MethodInfo method, Type type) =>
|
||||
Delegate.CreateDelegate(type, method);
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,72 @@
|
|||
#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
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Compatibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="PropertyInfo"/>, effectively providing
|
||||
/// the familiar members from previous desktop framework versions while
|
||||
/// targeting the newer releases, .NET Core etc.
|
||||
/// </summary>
|
||||
internal static class PropertyInfoExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the public getter of a property, or null if there is no such getter
|
||||
/// (either because it's read-only, or the getter isn't public).
|
||||
/// </summary>
|
||||
internal static MethodInfo GetGetMethod(this PropertyInfo target)
|
||||
{
|
||||
#if NET35
|
||||
var method = target.GetGetMethod();
|
||||
#else
|
||||
var method = target.GetMethod;
|
||||
#endif
|
||||
return method != null && method.IsPublic ? method : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the public setter of a property, or null if there is no such setter
|
||||
/// (either because it's write-only, or the setter isn't public).
|
||||
/// </summary>
|
||||
internal static MethodInfo GetSetMethod(this PropertyInfo target)
|
||||
{
|
||||
#if NET35
|
||||
var method = target.GetSetMethod();
|
||||
#else
|
||||
var method = target.SetMethod;
|
||||
#endif
|
||||
return method != null && method.IsPublic ? method : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#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
|
||||
|
||||
#if NET35
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LC.Google.Protobuf.Compatibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Stream"/> in order to provide
|
||||
/// backwards compatibility with .NET 3.5
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
// 81920 seems to be the default buffer size used in .NET 4.5.1
|
||||
private const int BUFFER_SIZE = 81920;
|
||||
|
||||
/// <summary>
|
||||
/// Write the contents of the current stream to the destination stream
|
||||
/// </summary>
|
||||
public static void CopyTo(this Stream source, Stream destination)
|
||||
{
|
||||
if (destination == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int numBytesRead;
|
||||
while ((numBytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
destination.Write(buffer, 0, numBytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,106 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
#if !NET35
|
||||
namespace LC.Google.Protobuf.Compatibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods on Type that just proxy to TypeInfo.
|
||||
/// These are used to support the new type system from .NET 4.5, without
|
||||
/// having calls to GetTypeInfo all over the place. While the methods here are meant to be
|
||||
/// broadly compatible with the desktop framework, there are some subtle differences in behaviour - but
|
||||
/// they're not expected to affect our use cases. While the class is internal, that should be fine: we can
|
||||
/// evaluate each new use appropriately.
|
||||
/// </summary>
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// See https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom
|
||||
/// </summary>
|
||||
internal static bool IsAssignableFrom(this Type target, Type c)
|
||||
{
|
||||
return target.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a representation of the public property associated with the given name in the given type,
|
||||
/// including inherited properties or null if there is no such public property.
|
||||
/// Here, "public property" means a property where either the getter, or the setter, or both, is public.
|
||||
/// </summary>
|
||||
internal static PropertyInfo GetProperty(this Type target, string name)
|
||||
{
|
||||
// GetDeclaredProperty only returns properties declared in the given type, so we need to recurse.
|
||||
while (target != null)
|
||||
{
|
||||
var typeInfo = target.GetTypeInfo();
|
||||
var ret = typeInfo.GetDeclaredProperty(name);
|
||||
if (ret != null && ((ret.CanRead && ret.GetMethod.IsPublic) || (ret.CanWrite && ret.SetMethod.IsPublic)))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
target = typeInfo.BaseType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a representation of the public method associated with the given name in the given type,
|
||||
/// including inherited methods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This has a few differences compared with Type.GetMethod in the desktop framework. It will throw
|
||||
/// if there is an ambiguous match even between a private method and a public one, but it *won't* throw
|
||||
/// if there are two overloads at different levels in the type hierarchy (e.g. class Base declares public void Foo(int) and
|
||||
/// class Child : Base declares public void Foo(long)).
|
||||
/// </remarks>
|
||||
/// <exception cref="AmbiguousMatchException">One type in the hierarchy declared more than one method with the same name</exception>
|
||||
internal static MethodInfo GetMethod(this Type target, string name)
|
||||
{
|
||||
// GetDeclaredMethod only returns methods declared in the given type, so we need to recurse.
|
||||
while (target != null)
|
||||
{
|
||||
var typeInfo = target.GetTypeInfo();
|
||||
var ret = typeInfo.GetDeclaredMethod(name);
|
||||
if (ret != null && ret.IsPublic)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
target = typeInfo.BaseType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,119 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a non-generic extension definition. This API is experimental and subject to change.
|
||||
/// </summary>
|
||||
public abstract class Extension
|
||||
{
|
||||
internal abstract Type TargetType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal use. Creates a new extension with the specified field number.
|
||||
/// </summary>
|
||||
protected Extension(int fieldNumber)
|
||||
{
|
||||
FieldNumber = fieldNumber;
|
||||
}
|
||||
|
||||
internal abstract IExtensionValue CreateValue();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the field number of this extension
|
||||
/// </summary>
|
||||
public int FieldNumber { get; }
|
||||
|
||||
internal abstract bool IsRepeated { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a type-safe extension identifier used for getting and setting single extension values in <see cref="IExtendableMessage{T}"/> instances.
|
||||
/// This API is experimental and subject to change.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTarget">The message type this field applies to</typeparam>
|
||||
/// <typeparam name="TValue">The field value type of this extension</typeparam>
|
||||
public sealed class Extension<TTarget, TValue> : Extension where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
private readonly FieldCodec<TValue> codec;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new extension identifier with the specified field number and codec
|
||||
/// </summary>
|
||||
public Extension(int fieldNumber, FieldCodec<TValue> codec) : base(fieldNumber)
|
||||
{
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
internal TValue DefaultValue => codec.DefaultValue;
|
||||
|
||||
internal override Type TargetType => typeof(TTarget);
|
||||
|
||||
internal override bool IsRepeated => false;
|
||||
|
||||
internal override IExtensionValue CreateValue()
|
||||
{
|
||||
return new ExtensionValue<TValue>(codec);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a type-safe extension identifier used for getting repeated extension values in <see cref="IExtendableMessage{T}"/> instances.
|
||||
/// This API is experimental and subject to change.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTarget">The message type this field applies to</typeparam>
|
||||
/// <typeparam name="TValue">The repeated field value type of this extension</typeparam>
|
||||
public sealed class RepeatedExtension<TTarget, TValue> : Extension where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
private readonly FieldCodec<TValue> codec;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new repeated extension identifier with the specified field number and codec
|
||||
/// </summary>
|
||||
public RepeatedExtension(int fieldNumber, FieldCodec<TValue> codec) : base(fieldNumber)
|
||||
{
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
internal override Type TargetType => typeof(TTarget);
|
||||
|
||||
internal override bool IsRepeated => true;
|
||||
|
||||
internal override IExtensionValue CreateValue()
|
||||
{
|
||||
return new RepeatedExtensionValue<TValue>(codec);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extensions to messages while parsing. This API is experimental and subject to change.
|
||||
/// </summary>
|
||||
public sealed class ExtensionRegistry : ICollection<Extension>, IDeepCloneable<ExtensionRegistry>
|
||||
{
|
||||
internal sealed class ExtensionComparer : IEqualityComparer<Extension>
|
||||
{
|
||||
public bool Equals(Extension a, Extension b)
|
||||
{
|
||||
return new ObjectIntPair<Type>(a.TargetType, a.FieldNumber).Equals(new ObjectIntPair<Type>(b.TargetType, b.FieldNumber));
|
||||
}
|
||||
public int GetHashCode(Extension a)
|
||||
{
|
||||
return new ObjectIntPair<Type>(a.TargetType, a.FieldNumber).GetHashCode();
|
||||
}
|
||||
|
||||
internal static ExtensionComparer Instance = new ExtensionComparer();
|
||||
}
|
||||
private IDictionary<ObjectIntPair<Type>, Extension> extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new empty extension registry
|
||||
/// </summary>
|
||||
public ExtensionRegistry()
|
||||
{
|
||||
extensions = new Dictionary<ObjectIntPair<Type>, Extension>();
|
||||
}
|
||||
|
||||
private ExtensionRegistry(IDictionary<ObjectIntPair<Type>, Extension> collection)
|
||||
{
|
||||
extensions = collection.ToDictionary(k => k.Key, v => v.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of extensions in this extension registry
|
||||
/// </summary>
|
||||
public int Count => extensions.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the registry is readonly
|
||||
/// </summary>
|
||||
bool ICollection<Extension>.IsReadOnly => false;
|
||||
|
||||
internal bool ContainsInputField(uint lastTag, Type target, out Extension extension)
|
||||
{
|
||||
return extensions.TryGetValue(new ObjectIntPair<Type>(target, WireFormat.GetTagFieldNumber(lastTag)), out extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified extension to the registry
|
||||
/// </summary>
|
||||
public void Add(Extension extension)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(extension, nameof(extension));
|
||||
|
||||
extensions.Add(new ObjectIntPair<Type>(extension.TargetType, extension.FieldNumber), extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified extensions to the registry
|
||||
/// </summary>
|
||||
public void AddRange(IEnumerable<Extension> extensions)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(extensions, nameof(extensions));
|
||||
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
Add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the registry of all values
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
extensions.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the extension registry contains the specified extension
|
||||
/// </summary>
|
||||
public bool Contains(Extension item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(item, nameof(item));
|
||||
|
||||
return extensions.ContainsKey(new ObjectIntPair<Type>(item.TargetType, item.FieldNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the arrays in the registry set to the specified array at the specified index
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy to</param>
|
||||
/// <param name="arrayIndex">The array index to start at</param>
|
||||
void ICollection<Extension>.CopyTo(Extension[] array, int arrayIndex)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(array, nameof(array));
|
||||
if (arrayIndex < 0 || arrayIndex >= array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException("The provided array is shorter than the number of elements in the registry");
|
||||
}
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
Extension extension = array[i];
|
||||
extensions.Add(new ObjectIntPair<Type>(extension.TargetType, extension.FieldNumber), extension);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator to enumerate through the items in the registry
|
||||
/// </summary>
|
||||
/// <returns>Returns an enumerator for the extensions in this registry</returns>
|
||||
public IEnumerator<Extension> GetEnumerator()
|
||||
{
|
||||
return extensions.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified extension from the set
|
||||
/// </summary>
|
||||
/// <param name="item">The extension</param>
|
||||
/// <returns><c>true</c> if the extension was removed, otherwise <c>false</c></returns>
|
||||
public bool Remove(Extension item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(item, nameof(item));
|
||||
|
||||
return extensions.Remove(new ObjectIntPair<Type>(item.TargetType, item.FieldNumber));
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Clones the registry into a new registry
|
||||
/// </summary>
|
||||
public ExtensionRegistry Clone()
|
||||
{
|
||||
return new ExtensionRegistry(extensions);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods for managing <see cref="ExtensionSet{TTarget}"/>s with null checking.
|
||||
///
|
||||
/// Most users will not use this class directly and its API is experimental and subject to change.
|
||||
/// </summary>
|
||||
public static class ExtensionSet
|
||||
{
|
||||
private static bool TryGetValue<TTarget>(ref ExtensionSet<TTarget> set, Extension extension, out IExtensionValue value) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
return set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified extension
|
||||
/// </summary>
|
||||
public static TValue Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
IExtensionValue value;
|
||||
if (TryGetValue(ref set, extension, out value))
|
||||
{
|
||||
return ((ExtensionValue<TValue>)value).GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
return extension.DefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified repeated extension or null if it doesn't exist in this set
|
||||
/// </summary>
|
||||
public static RepeatedField<TValue> Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
IExtensionValue value;
|
||||
if (TryGetValue(ref set, extension, out value))
|
||||
{
|
||||
return ((RepeatedExtensionValue<TValue>)value).GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified repeated extension, registering it if it doesn't exist
|
||||
/// </summary>
|
||||
public static RepeatedField<TValue> GetOrInitialize<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
IExtensionValue value;
|
||||
if (set == null)
|
||||
{
|
||||
value = extension.CreateValue();
|
||||
set = new ExtensionSet<TTarget>();
|
||||
set.ValuesByNumber.Add(extension.FieldNumber, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value))
|
||||
{
|
||||
value = extension.CreateValue();
|
||||
set.ValuesByNumber.Add(extension.FieldNumber, value);
|
||||
}
|
||||
}
|
||||
|
||||
return ((RepeatedExtensionValue<TValue>)value).GetValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the specified extension. This will make a new instance of ExtensionSet if the set is null.
|
||||
/// </summary>
|
||||
public static void Set<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension, TValue value) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
|
||||
|
||||
IExtensionValue extensionValue;
|
||||
if (set == null)
|
||||
{
|
||||
extensionValue = extension.CreateValue();
|
||||
set = new ExtensionSet<TTarget>();
|
||||
set.ValuesByNumber.Add(extension.FieldNumber, extensionValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out extensionValue))
|
||||
{
|
||||
extensionValue = extension.CreateValue();
|
||||
set.ValuesByNumber.Add(extension.FieldNumber, extensionValue);
|
||||
}
|
||||
}
|
||||
|
||||
((ExtensionValue<TValue>)extensionValue).SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the value of the specified extension is set
|
||||
/// </summary>
|
||||
public static bool Has<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
IExtensionValue value;
|
||||
return TryGetValue(ref set, extension, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the value of the specified extension
|
||||
/// </summary>
|
||||
public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
set.ValuesByNumber.Remove(extension.FieldNumber);
|
||||
if (set.ValuesByNumber.Count == 0)
|
||||
{
|
||||
set = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the value of the specified extension
|
||||
/// </summary>
|
||||
public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
set.ValuesByNumber.Remove(extension.FieldNumber);
|
||||
if (set.ValuesByNumber.Count == 0)
|
||||
{
|
||||
set = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to merge a field from the coded input, returning true if the field was merged.
|
||||
/// If the set is null or the field was not otherwise merged, this returns false.
|
||||
/// </summary>
|
||||
public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, CodedInputStream stream) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
ParseContext.Initialize(stream, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
return TryMergeFieldFrom<TTarget>(ref set, ref ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to merge a field from the coded input, returning true if the field was merged.
|
||||
/// If the set is null or the field was not otherwise merged, this returns false.
|
||||
/// </summary>
|
||||
public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, ref ParseContext ctx) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
Extension extension;
|
||||
int lastFieldNumber = WireFormat.GetTagFieldNumber(ctx.LastTag);
|
||||
|
||||
IExtensionValue extensionValue;
|
||||
if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out extensionValue))
|
||||
{
|
||||
extensionValue.MergeFrom(ref ctx);
|
||||
return true;
|
||||
}
|
||||
else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out extension))
|
||||
{
|
||||
IExtensionValue value = extension.CreateValue();
|
||||
value.MergeFrom(ref ctx);
|
||||
set = (set ?? new ExtensionSet<TTarget>());
|
||||
set.ValuesByNumber.Add(extension.FieldNumber, value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the second set into the first set, creating a new instance if first is null
|
||||
/// </summary>
|
||||
public static void MergeFrom<TTarget>(ref ExtensionSet<TTarget> first, ExtensionSet<TTarget> second) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
if (second == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (first == null)
|
||||
{
|
||||
first = new ExtensionSet<TTarget>();
|
||||
}
|
||||
foreach (var pair in second.ValuesByNumber)
|
||||
{
|
||||
IExtensionValue value;
|
||||
if (first.ValuesByNumber.TryGetValue(pair.Key, out value))
|
||||
{
|
||||
value.MergeFrom(pair.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cloned = pair.Value.Clone();
|
||||
first.ValuesByNumber[pair.Key] = cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the set into a new set. If the set is null, this returns null
|
||||
/// </summary>
|
||||
public static ExtensionSet<TTarget> Clone<TTarget>(ExtensionSet<TTarget> set) where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var newSet = new ExtensionSet<TTarget>();
|
||||
foreach (var pair in set.ValuesByNumber)
|
||||
{
|
||||
var cloned = pair.Value.Clone();
|
||||
newSet.ValuesByNumber[pair.Key] = cloned;
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for keeping track of extensions in messages.
|
||||
/// <see cref="IExtendableMessage{T}"/> methods route to this set.
|
||||
///
|
||||
/// Most users will not need to use this class directly
|
||||
/// </summary>
|
||||
/// <typeparam name="TTarget">The message type that extensions in this set target</typeparam>
|
||||
public sealed class ExtensionSet<TTarget> where TTarget : IExtendableMessage<TTarget>
|
||||
{
|
||||
internal Dictionary<int, IExtensionValue> ValuesByNumber { get; } = new Dictionary<int, IExtensionValue>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash code of the set
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int ret = typeof(TTarget).GetHashCode();
|
||||
foreach (KeyValuePair<int, IExtensionValue> field in ValuesByNumber)
|
||||
{
|
||||
// Use ^ here to make the field order irrelevant.
|
||||
int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode();
|
||||
ret ^= hash;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this set is equal to the other object
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
ExtensionSet<TTarget> otherSet = other as ExtensionSet<TTarget>;
|
||||
if (ValuesByNumber.Count != otherSet.ValuesByNumber.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var pair in ValuesByNumber)
|
||||
{
|
||||
IExtensionValue secondValue;
|
||||
if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out secondValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!pair.Value.Equals(secondValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this extension set
|
||||
/// </summary>
|
||||
public int CalculateSize()
|
||||
{
|
||||
int size = 0;
|
||||
foreach (var value in ValuesByNumber.Values)
|
||||
{
|
||||
size += value.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the extension values in this set to the output stream
|
||||
/// </summary>
|
||||
public void WriteTo(CodedOutputStream stream)
|
||||
{
|
||||
|
||||
WriteContext.Initialize(stream, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WriteTo(ref ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the extension values in this set to the write context
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public void WriteTo(ref WriteContext ctx)
|
||||
{
|
||||
foreach (var value in ValuesByNumber.Values)
|
||||
{
|
||||
value.WriteTo(ref ctx);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsInitialized()
|
||||
{
|
||||
return ValuesByNumber.Values.All(v => v.IsInitialized());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
internal interface IExtensionValue : IEquatable<IExtensionValue>, IDeepCloneable<IExtensionValue>
|
||||
{
|
||||
void MergeFrom(ref ParseContext ctx);
|
||||
|
||||
void MergeFrom(IExtensionValue value);
|
||||
void WriteTo(ref WriteContext ctx);
|
||||
int CalculateSize();
|
||||
bool IsInitialized();
|
||||
}
|
||||
|
||||
internal sealed class ExtensionValue<T> : IExtensionValue
|
||||
{
|
||||
private T field;
|
||||
private FieldCodec<T> codec;
|
||||
|
||||
internal ExtensionValue(FieldCodec<T> codec)
|
||||
{
|
||||
this.codec = codec;
|
||||
field = codec.DefaultValue;
|
||||
}
|
||||
|
||||
public int CalculateSize()
|
||||
{
|
||||
return codec.CalculateUnconditionalSizeWithTag(field);
|
||||
}
|
||||
|
||||
public IExtensionValue Clone()
|
||||
{
|
||||
return new ExtensionValue<T>(codec)
|
||||
{
|
||||
field = field is IDeepCloneable<T> ? (field as IDeepCloneable<T>).Clone() : field
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(IExtensionValue other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return other is ExtensionValue<T>
|
||||
&& codec.Equals((other as ExtensionValue<T>).codec)
|
||||
&& Equals(field, (other as ExtensionValue<T>).field);
|
||||
// we check for equality in the codec since we could have equal field values however the values could be written in different ways
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + field.GetHashCode();
|
||||
hash = hash * 31 + codec.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public void MergeFrom(ref ParseContext ctx)
|
||||
{
|
||||
codec.ValueMerger(ref ctx, ref field);
|
||||
}
|
||||
|
||||
public void MergeFrom(IExtensionValue value)
|
||||
{
|
||||
if (value is ExtensionValue<T>)
|
||||
{
|
||||
var extensionValue = value as ExtensionValue<T>;
|
||||
codec.FieldMerger(ref field, extensionValue.field);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTo(ref WriteContext ctx)
|
||||
{
|
||||
ctx.WriteTag(codec.Tag);
|
||||
codec.ValueWriter(ref ctx, field);
|
||||
if (codec.EndTag != 0)
|
||||
{
|
||||
ctx.WriteTag(codec.EndTag);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetValue() => field;
|
||||
|
||||
public void SetValue(T value)
|
||||
{
|
||||
field = value;
|
||||
}
|
||||
|
||||
public bool IsInitialized()
|
||||
{
|
||||
if (field is IMessage)
|
||||
{
|
||||
return (field as IMessage).IsInitialized();
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RepeatedExtensionValue<T> : IExtensionValue
|
||||
{
|
||||
private RepeatedField<T> field;
|
||||
private readonly FieldCodec<T> codec;
|
||||
|
||||
internal RepeatedExtensionValue(FieldCodec<T> codec)
|
||||
{
|
||||
this.codec = codec;
|
||||
field = new RepeatedField<T>();
|
||||
}
|
||||
|
||||
public int CalculateSize()
|
||||
{
|
||||
return field.CalculateSize(codec);
|
||||
}
|
||||
|
||||
public IExtensionValue Clone()
|
||||
{
|
||||
return new RepeatedExtensionValue<T>(codec)
|
||||
{
|
||||
field = field.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(IExtensionValue other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return other is RepeatedExtensionValue<T>
|
||||
&& field.Equals((other as RepeatedExtensionValue<T>).field)
|
||||
&& codec.Equals((other as RepeatedExtensionValue<T>).codec);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + field.GetHashCode();
|
||||
hash = hash * 31 + codec.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public void MergeFrom(ref ParseContext ctx)
|
||||
{
|
||||
field.AddEntriesFrom(ref ctx, codec);
|
||||
}
|
||||
|
||||
public void MergeFrom(IExtensionValue value)
|
||||
{
|
||||
if (value is RepeatedExtensionValue<T>)
|
||||
{
|
||||
field.Add((value as RepeatedExtensionValue<T>).field);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTo(ref WriteContext ctx)
|
||||
{
|
||||
field.WriteTo(ref ctx, codec);
|
||||
}
|
||||
|
||||
public RepeatedField<T> GetValue() => field;
|
||||
|
||||
public bool IsInitialized()
|
||||
{
|
||||
for (int i = 0; i < field.Count; i++)
|
||||
{
|
||||
var element = field[i];
|
||||
if (element is IMessage)
|
||||
{
|
||||
if (!(element as IMessage).IsInitialized())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,887 @@
|
|||
#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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
using LC.Google.Protobuf.WellKnownTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory methods for <see cref="FieldCodec{T}"/>.
|
||||
/// </summary>
|
||||
public static class FieldCodec
|
||||
{
|
||||
// TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<string> ForString(uint tag)
|
||||
{
|
||||
return FieldCodec.ForString(tag, "");
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<ByteString> ForBytes(uint tag)
|
||||
{
|
||||
return FieldCodec.ForBytes(tag, ByteString.Empty);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<bool> ForBool(uint tag)
|
||||
{
|
||||
return FieldCodec.ForBool(tag, false);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<int> ForInt32(uint tag)
|
||||
{
|
||||
return FieldCodec.ForInt32(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<int> ForSInt32(uint tag)
|
||||
{
|
||||
return FieldCodec.ForSInt32(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<uint> ForFixed32(uint tag)
|
||||
{
|
||||
return FieldCodec.ForFixed32(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<int> ForSFixed32(uint tag)
|
||||
{
|
||||
return FieldCodec.ForSFixed32(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<uint> ForUInt32(uint tag)
|
||||
{
|
||||
return FieldCodec.ForUInt32(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<long> ForInt64(uint tag)
|
||||
{
|
||||
return FieldCodec.ForInt64(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<long> ForSInt64(uint tag)
|
||||
{
|
||||
return FieldCodec.ForSInt64(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<ulong> ForFixed64(uint tag)
|
||||
{
|
||||
return FieldCodec.ForFixed64(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<long> ForSFixed64(uint tag)
|
||||
{
|
||||
return FieldCodec.ForSFixed64(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<ulong> ForUInt64(uint tag)
|
||||
{
|
||||
return FieldCodec.ForUInt64(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<float> ForFloat(uint tag)
|
||||
{
|
||||
return FieldCodec.ForFloat(tag, 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<double> ForDouble(uint tag)
|
||||
{
|
||||
return FieldCodec.ForDouble(tag, 0);
|
||||
}
|
||||
|
||||
// Enums are tricky. We can probably use expression trees to build these delegates automatically,
|
||||
// but it's easy to generate the code for it.
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
|
||||
{
|
||||
return FieldCodec.ForEnum(tag, toInt32, fromInt32, default(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a string field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<string> ForString(uint tag, string defaultValue)
|
||||
{
|
||||
return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (ref WriteContext ctx, string value) => ctx.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a bytes field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<ByteString> ForBytes(uint tag, ByteString defaultValue)
|
||||
{
|
||||
return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (ref WriteContext ctx, ByteString value) => ctx.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a bool field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<bool> ForBool(uint tag, bool defaultValue)
|
||||
{
|
||||
return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (ref WriteContext ctx, bool value) => ctx.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an int32 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<int> ForInt32(uint tag, int defaultValue)
|
||||
{
|
||||
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (ref WriteContext output, int value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an sint32 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<int> ForSInt32(uint tag, int defaultValue)
|
||||
{
|
||||
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (ref WriteContext output, int value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a fixed32 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<uint> ForFixed32(uint tag, uint defaultValue)
|
||||
{
|
||||
return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (ref WriteContext output, uint value) => output.WriteFixed32(value), 4, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an sfixed32 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<int> ForSFixed32(uint tag, int defaultValue)
|
||||
{
|
||||
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (ref WriteContext output, int value) => output.WriteSFixed32(value), 4, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a uint32 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<uint> ForUInt32(uint tag, uint defaultValue)
|
||||
{
|
||||
return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (ref WriteContext output, uint value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an int64 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<long> ForInt64(uint tag, long defaultValue)
|
||||
{
|
||||
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (ref WriteContext output, long value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an sint64 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<long> ForSInt64(uint tag, long defaultValue)
|
||||
{
|
||||
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (ref WriteContext output, long value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a fixed64 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<ulong> ForFixed64(uint tag, ulong defaultValue)
|
||||
{
|
||||
return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (ref WriteContext output, ulong value) => output.WriteFixed64(value), 8, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for an sfixed64 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<long> ForSFixed64(uint tag, long defaultValue)
|
||||
{
|
||||
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (ref WriteContext output, long value) => output.WriteSFixed64(value), 8, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a uint64 field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<ulong> ForUInt64(uint tag, ulong defaultValue)
|
||||
{
|
||||
return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (ref WriteContext output, ulong value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a float field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<float> ForFloat(uint tag, float defaultValue)
|
||||
{
|
||||
return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (ref WriteContext output, float value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a double field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<double> ForDouble(uint tag, double defaultValue)
|
||||
{
|
||||
return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (ref WriteContext output, double value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
|
||||
}
|
||||
|
||||
// Enums are tricky. We can probably use expression trees to build these delegates automatically,
|
||||
// but it's easy to generate the code for it.
|
||||
|
||||
/// <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>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>A codec for the given tag.</returns>
|
||||
public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32, T defaultValue)
|
||||
{
|
||||
return new FieldCodec<T>((ref ParseContext ctx) => fromInt32(
|
||||
ctx.ReadEnum()),
|
||||
(ref WriteContext output, T value) => output.WriteEnum(toInt32(value)),
|
||||
value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag, defaultValue);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : class, IMessage<T>
|
||||
{
|
||||
return new FieldCodec<T>(
|
||||
(ref ParseContext ctx) =>
|
||||
{
|
||||
T message = parser.CreateTemplate();
|
||||
ctx.ReadMessage(message);
|
||||
return message;
|
||||
},
|
||||
(ref WriteContext output, T value) => output.WriteMessage(value),
|
||||
(ref ParseContext ctx, ref T v) =>
|
||||
{
|
||||
if (v == null)
|
||||
{
|
||||
v = parser.CreateTemplate();
|
||||
}
|
||||
|
||||
ctx.ReadMessage(v);
|
||||
},
|
||||
(ref T v, T v2) =>
|
||||
{
|
||||
if (v2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (v == null)
|
||||
{
|
||||
v = v2.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
v.MergeFrom(v2);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
message => CodedOutputStream.ComputeMessageSize(message), tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a codec suitable for a group field with the given tag.
|
||||
/// </summary>
|
||||
/// <param name="startTag">The start group tag.</param>
|
||||
/// <param name="endTag">The end group tag.</param>
|
||||
/// <param name="parser">A parser to use for the group message type.</param>
|
||||
/// <returns>A codec for given tag</returns>
|
||||
public static FieldCodec<T> ForGroup<T>(uint startTag, uint endTag, MessageParser<T> parser) where T : class, IMessage<T>
|
||||
{
|
||||
return new FieldCodec<T>(
|
||||
(ref ParseContext ctx) =>
|
||||
{
|
||||
T message = parser.CreateTemplate();
|
||||
ctx.ReadGroup(message);
|
||||
return message;
|
||||
},
|
||||
(ref WriteContext output, T value) => output.WriteGroup(value),
|
||||
(ref ParseContext ctx, ref T v) =>
|
||||
{
|
||||
if (v == null)
|
||||
{
|
||||
v = parser.CreateTemplate();
|
||||
}
|
||||
|
||||
ctx.ReadGroup(v);
|
||||
},
|
||||
(ref T v, T v2) =>
|
||||
{
|
||||
if (v2 == null)
|
||||
{
|
||||
return v == null;
|
||||
}
|
||||
else if (v == null)
|
||||
{
|
||||
v = v2.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
v.MergeFrom(v2);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
message => CodedOutputStream.ComputeGroupSize(message), startTag, endTag);
|
||||
}
|
||||
|
||||
/// <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>(
|
||||
(ref ParseContext ctx) => WrapperCodecs.Read<T>(ref ctx, nestedCodec),
|
||||
(ref WriteContext output, T value) => WrapperCodecs.Write<T>(ref output, value, nestedCodec),
|
||||
(ref ParseContext ctx, ref T v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
|
||||
(ref T v, T v2) => { v = v2; return v == null; },
|
||||
value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
|
||||
tag, 0,
|
||||
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?>(
|
||||
WrapperCodecs.GetReader<T>(),
|
||||
(ref WriteContext output, T? value) => WrapperCodecs.Write<T>(ref output, value.Value, nestedCodec),
|
||||
(ref ParseContext ctx, ref T? v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
|
||||
(ref T? v, T? v2) => { if (v2.HasValue) { v = v2; } return v.HasValue; },
|
||||
value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
|
||||
tag, 0,
|
||||
null); // Default value for the wrapper
|
||||
}
|
||||
|
||||
/// <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>
|
||||
private static class WrapperCodecs
|
||||
{
|
||||
private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object>
|
||||
{
|
||||
{ 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)) }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<System.Type, object> Readers = new Dictionary<System.Type, object>
|
||||
{
|
||||
// TODO: Provide more optimized readers.
|
||||
{ typeof(bool), (ValueReader<bool?>)ParsingPrimitivesWrappers.ReadBoolWrapper },
|
||||
{ typeof(int), (ValueReader<int?>)ParsingPrimitivesWrappers.ReadInt32Wrapper },
|
||||
{ typeof(long), (ValueReader<long?>)ParsingPrimitivesWrappers.ReadInt64Wrapper },
|
||||
{ typeof(uint), (ValueReader<uint?>)ParsingPrimitivesWrappers.ReadUInt32Wrapper },
|
||||
{ typeof(ulong), (ValueReader<ulong?>)ParsingPrimitivesWrappers.ReadUInt64Wrapper },
|
||||
{ typeof(float), BitConverter.IsLittleEndian ?
|
||||
(ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian :
|
||||
(ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperSlow },
|
||||
{ typeof(double), BitConverter.IsLittleEndian ?
|
||||
(ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian :
|
||||
(ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperSlow },
|
||||
// `string` and `ByteString` less performance-sensitive. Do not implement for now.
|
||||
{ typeof(string), null },
|
||||
{ typeof(ByteString), null },
|
||||
};
|
||||
|
||||
/// <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 ValueReader<T?> GetReader<T>() where T : struct
|
||||
{
|
||||
object value;
|
||||
if (!Readers.TryGetValue(typeof(T), out value))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid type argument requested for wrapper reader: " + typeof(T));
|
||||
}
|
||||
if (value == null)
|
||||
{
|
||||
// Return default unoptimized reader for the wrapper type.
|
||||
var nestedCoded = GetCodec<T>();
|
||||
return (ref ParseContext ctx) => Read<T>(ref ctx, nestedCoded);
|
||||
}
|
||||
// Return optimized read for the wrapper type.
|
||||
return (ValueReader<T?>)value;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
internal static T Read<T>(ref ParseContext ctx, FieldCodec<T> codec)
|
||||
{
|
||||
int length = ctx.ReadLength();
|
||||
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
|
||||
|
||||
uint tag;
|
||||
T value = codec.DefaultValue;
|
||||
while ((tag = ctx.ReadTag()) != 0)
|
||||
{
|
||||
if (tag == codec.Tag)
|
||||
{
|
||||
value = codec.Read(ref ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
}
|
||||
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
|
||||
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
internal static void Write<T>(ref WriteContext ctx, T value, FieldCodec<T> codec)
|
||||
{
|
||||
ctx.WriteLength(codec.CalculateSizeWithTag(value));
|
||||
codec.WriteTagAndValue(ref ctx, value);
|
||||
}
|
||||
|
||||
internal static int CalculateSize<T>(T value, FieldCodec<T> codec)
|
||||
{
|
||||
int fieldLength = codec.CalculateSizeWithTag(value);
|
||||
return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal delegate TValue ValueReader<out TValue>(ref ParseContext ctx);
|
||||
internal delegate void ValueWriter<T>(ref WriteContext ctx, T value);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </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>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public sealed class FieldCodec<T>
|
||||
{
|
||||
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
|
||||
private static readonly T DefaultDefault;
|
||||
// Only non-nullable value types support packing. This is the simplest way of detecting that.
|
||||
private static readonly bool TypeSupportsPacking = default(T) != null;
|
||||
|
||||
/// <summary>
|
||||
/// Merges an input stream into a value
|
||||
/// </summary>
|
||||
internal delegate void InputMerger(ref ParseContext ctx, ref T value);
|
||||
|
||||
/// <summary>
|
||||
/// Merges a value into a reference to another value, returning a boolean if the value was set
|
||||
/// </summary>
|
||||
internal delegate bool ValuesMerger(ref T value, T other);
|
||||
|
||||
static FieldCodec()
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
DefaultDefault = (T)(object)"";
|
||||
}
|
||||
else if (typeof(T) == typeof(ByteString))
|
||||
{
|
||||
DefaultDefault = (T)(object)ByteString.Empty;
|
||||
}
|
||||
// Otherwise it's the default value of the CLR type
|
||||
}
|
||||
|
||||
internal static bool IsPackedRepeatedField(uint tag) =>
|
||||
TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited;
|
||||
|
||||
internal bool PackedRepeatedField { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a delegate to write a value (unconditionally) to a coded output stream.
|
||||
/// </summary>
|
||||
internal ValueWriter<T> ValueWriter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size calculator for just a value.
|
||||
/// </summary>
|
||||
internal Func<T, int> ValueSizeCalculator { get; }
|
||||
|
||||
/// <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>
|
||||
internal ValueReader<T> ValueReader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a delegate to merge a value from a coded input stream.
|
||||
/// It is assumed that the stream is already positioned on the appropriate tag
|
||||
/// </summary>
|
||||
internal InputMerger ValueMerger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a delegate to merge two values together.
|
||||
/// </summary>
|
||||
internal ValuesMerger FieldMerger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the fixed size for an entry, or 0 if sizes vary.
|
||||
/// </summary>
|
||||
internal int FixedSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tag of the codec.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tag of the codec.
|
||||
/// </value>
|
||||
internal uint Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end tag of the codec or 0 if there is no end tag
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The end tag of the codec.
|
||||
/// </value>
|
||||
internal uint EndTag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default value of the codec's type.
|
||||
/// </value>
|
||||
internal T DefaultValue { get; }
|
||||
|
||||
private readonly int tagSize;
|
||||
|
||||
internal FieldCodec(
|
||||
ValueReader<T> reader,
|
||||
ValueWriter<T> writer,
|
||||
int fixedSize,
|
||||
uint tag,
|
||||
T defaultValue) : this(reader, writer, _ => fixedSize, tag, defaultValue)
|
||||
{
|
||||
FixedSize = fixedSize;
|
||||
}
|
||||
|
||||
internal FieldCodec(
|
||||
ValueReader<T> reader,
|
||||
ValueWriter<T> writer,
|
||||
Func<T, int> sizeCalculator,
|
||||
uint tag,
|
||||
T defaultValue) : this(reader, writer, (ref ParseContext ctx, ref T v) => v = reader(ref ctx), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
internal FieldCodec(
|
||||
ValueReader<T> reader,
|
||||
ValueWriter<T> writer,
|
||||
InputMerger inputMerger,
|
||||
ValuesMerger valuesMerger,
|
||||
Func<T, int> sizeCalculator,
|
||||
uint tag,
|
||||
uint endTag = 0) : this(reader, writer, inputMerger, valuesMerger, sizeCalculator, tag, endTag, DefaultDefault)
|
||||
{
|
||||
}
|
||||
|
||||
internal FieldCodec(
|
||||
ValueReader<T> reader,
|
||||
ValueWriter<T> writer,
|
||||
InputMerger inputMerger,
|
||||
ValuesMerger valuesMerger,
|
||||
Func<T, int> sizeCalculator,
|
||||
uint tag,
|
||||
uint endTag,
|
||||
T defaultValue)
|
||||
{
|
||||
ValueReader = reader;
|
||||
ValueWriter = writer;
|
||||
ValueMerger = inputMerger;
|
||||
FieldMerger = valuesMerger;
|
||||
ValueSizeCalculator = sizeCalculator;
|
||||
FixedSize = 0;
|
||||
Tag = tag;
|
||||
EndTag = endTag;
|
||||
DefaultValue = defaultValue;
|
||||
tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
|
||||
if (endTag != 0)
|
||||
tagSize += CodedOutputStream.ComputeRawVarint32Size(endTag);
|
||||
// Detect packed-ness once, so we can check for it within RepeatedField<T>.
|
||||
PackedRepeatedField = IsPackedRepeatedField(tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a tag and the given value, *if* the value is not the default.
|
||||
/// </summary>
|
||||
public void WriteTagAndValue(CodedOutputStream output, T value)
|
||||
{
|
||||
WriteContext.Initialize(output, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WriteTagAndValue(ref ctx, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(output);
|
||||
}
|
||||
|
||||
|
||||
//if (!IsDefault(value))
|
||||
//{
|
||||
// output.WriteTag(Tag);
|
||||
// ValueWriter(output, value);
|
||||
// if (EndTag != 0)
|
||||
// {
|
||||
// output.WriteTag(EndTag);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a tag and the given value, *if* the value is not the default.
|
||||
/// </summary>
|
||||
public void WriteTagAndValue(ref WriteContext ctx, T value)
|
||||
{
|
||||
if (!IsDefault(value))
|
||||
{
|
||||
ctx.WriteTag(Tag);
|
||||
ValueWriter(ref ctx, value);
|
||||
if (EndTag != 0)
|
||||
{
|
||||
ctx.WriteTag(EndTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public T Read(CodedInputStream input)
|
||||
{
|
||||
ParseContext.Initialize(input, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
return ValueReader(ref ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value of the codec type from the given <see cref="ParseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The parse context to read from.</param>
|
||||
/// <returns>The value read.</returns>
|
||||
public T Read(ref ParseContext ctx)
|
||||
{
|
||||
return ValueReader(ref ctx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size required to write the given value, with a tag,
|
||||
/// if the value is not the default.
|
||||
/// </summary>
|
||||
public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size required to write the given value, with a tag, even
|
||||
/// if the value is the default.
|
||||
/// </summary>
|
||||
internal int CalculateUnconditionalSizeWithTag(T value) => ValueSizeCalculator(value) + tagSize;
|
||||
|
||||
private bool IsDefault(T value) => EqualityComparer.Equals(value, DefaultValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
#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
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
using LC.Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>A tree representation of a FieldMask. Each leaf node in this tree represent
|
||||
/// a field path in the FieldMask.</para>
|
||||
///
|
||||
/// <para>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:</para>
|
||||
/// <code>
|
||||
/// [root] -+- foo -+- bar
|
||||
/// | |
|
||||
/// | +- baz
|
||||
/// |
|
||||
/// +- bar --- baz
|
||||
/// </code>
|
||||
///
|
||||
/// <para>By representing FieldMasks with this tree structure we can easily convert
|
||||
/// a FieldMask to a canonical form, merge two FieldMasks, calculate the
|
||||
/// intersection to two FieldMasks and traverse all fields specified by the
|
||||
/// FieldMask in a message tree.</para>
|
||||
/// </summary>
|
||||
internal sealed class FieldMaskTree
|
||||
{
|
||||
private const char FIELD_PATH_SEPARATOR = '.';
|
||||
|
||||
internal sealed class Node
|
||||
{
|
||||
public Dictionary<string, Node> Children { get; } = new Dictionary<string, Node>();
|
||||
}
|
||||
|
||||
private readonly Node root = new Node();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty FieldMaskTree.
|
||||
/// </summary>
|
||||
public FieldMaskTree()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a FieldMaskTree for a given FieldMask.
|
||||
/// </summary>
|
||||
public FieldMaskTree(FieldMask mask)
|
||||
{
|
||||
MergeFromFieldMask(mask);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToFieldMask().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a field path to the tree. In a FieldMask, every field path matches the
|
||||
/// specified field as well as all its sub-fields. For example, a field path
|
||||
/// "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding
|
||||
/// a field path to the tree, redundant sub-paths will be removed. That is,
|
||||
/// after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it
|
||||
/// exists, which will turn the tree node for "foo.bar" to a leaf node.
|
||||
/// Likewise, if the field path to add is a sub-path of an existing leaf node,
|
||||
/// nothing will be changed in the tree.
|
||||
/// </summary>
|
||||
public FieldMaskTree AddFieldPath(string path)
|
||||
{
|
||||
var parts = path.Split(FIELD_PATH_SEPARATOR);
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var node = root;
|
||||
var createNewBranch = false;
|
||||
|
||||
// Find the matching node in the tree.
|
||||
foreach (var part in parts)
|
||||
{
|
||||
// Check whether the path matches an existing leaf node.
|
||||
if (!createNewBranch
|
||||
&& node != root
|
||||
&& node.Children.Count == 0)
|
||||
{
|
||||
// The path to add is a sub-path of an existing leaf node.
|
||||
return this;
|
||||
}
|
||||
|
||||
Node childNode;
|
||||
if (!node.Children.TryGetValue(part, out childNode))
|
||||
{
|
||||
createNewBranch = true;
|
||||
childNode = new Node();
|
||||
node.Children.Add(part, childNode);
|
||||
}
|
||||
node = childNode;
|
||||
}
|
||||
|
||||
// Turn the matching node into a leaf node (i.e., remove sub-paths).
|
||||
node.Children.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all field paths in a FieldMask into this tree.
|
||||
/// </summary>
|
||||
public FieldMaskTree MergeFromFieldMask(FieldMask mask)
|
||||
{
|
||||
foreach (var path in mask.Paths)
|
||||
{
|
||||
AddFieldPath(path);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this tree to a FieldMask.
|
||||
/// </summary>
|
||||
public FieldMask ToFieldMask()
|
||||
{
|
||||
var mask = new FieldMask();
|
||||
if (root.Children.Count != 0)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
GetFieldPaths(root, "", paths);
|
||||
mask.Paths.AddRange(paths);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers all field paths in a sub-tree.
|
||||
/// </summary>
|
||||
private void GetFieldPaths(Node node, string path, List<string> paths)
|
||||
{
|
||||
if (node.Children.Count == 0)
|
||||
{
|
||||
paths.Add(path);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var entry in node.Children)
|
||||
{
|
||||
var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key;
|
||||
GetFieldPaths(entry.Value, childPath, paths);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the intersection of this tree with the given <paramref name="path"/> to <paramref name="output"/>.
|
||||
/// </summary>
|
||||
public void IntersectFieldPath(string path, FieldMaskTree output)
|
||||
{
|
||||
if (root.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parts = path.Split(FIELD_PATH_SEPARATOR);
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var node = root;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (node != root
|
||||
&& node.Children.Count == 0)
|
||||
{
|
||||
// The given path is a sub-path of an existing leaf node in the tree.
|
||||
output.AddFieldPath(path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.Children.TryGetValue(part, out node))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We found a matching node for the path. All leaf children of this matching
|
||||
// node is in the intersection.
|
||||
var paths = new List<string>();
|
||||
GetFieldPaths(node, path, paths);
|
||||
foreach (var value in paths)
|
||||
{
|
||||
output.AddFieldPath(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all fields specified by this FieldMaskTree from <paramref name="source"/> to <paramref name="destination"/>.
|
||||
/// </summary>
|
||||
public void Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options)
|
||||
{
|
||||
if (source.Descriptor != destination.Descriptor)
|
||||
{
|
||||
throw new InvalidProtocolBufferException("Cannot merge messages of different types.");
|
||||
}
|
||||
|
||||
if (root.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Merge(root, "", source, destination, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all fields specified by a sub-tree from <paramref name="source"/> to <paramref name="destination"/>.
|
||||
/// </summary>
|
||||
private void Merge(
|
||||
Node node,
|
||||
string path,
|
||||
IMessage source,
|
||||
IMessage destination,
|
||||
FieldMask.MergeOptions options)
|
||||
{
|
||||
if (source.Descriptor != destination.Descriptor)
|
||||
{
|
||||
throw new InvalidProtocolBufferException($"source ({source.Descriptor}) and destination ({destination.Descriptor}) descriptor must be equal");
|
||||
}
|
||||
|
||||
var descriptor = source.Descriptor;
|
||||
foreach (var entry in node.Children)
|
||||
{
|
||||
var field = descriptor.FindFieldByName(entry.Key);
|
||||
if (field == null)
|
||||
{
|
||||
Debug.WriteLine($"Cannot find field \"{entry.Key}\" in message type \"{descriptor.FullName}\"");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.Value.Children.Count != 0)
|
||||
{
|
||||
if (field.IsRepeated
|
||||
|| field.FieldType != FieldType.Message)
|
||||
{
|
||||
Debug.WriteLine($"Field \"{field.FullName}\" is not a singular message field and cannot have sub-fields.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var sourceField = field.Accessor.GetValue(source);
|
||||
var destinationField = field.Accessor.GetValue(destination);
|
||||
if (sourceField == null
|
||||
&& destinationField == null)
|
||||
{
|
||||
// If the message field is not present in both source and destination, skip recursing
|
||||
// so we don't create unnecessary empty messages.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (destinationField == null)
|
||||
{
|
||||
// If we have to merge but the destination does not contain the field, create it.
|
||||
destinationField = field.MessageType.Parser.CreateTemplate();
|
||||
field.Accessor.SetValue(destination, destinationField);
|
||||
}
|
||||
|
||||
var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key;
|
||||
Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.IsRepeated)
|
||||
{
|
||||
if (options.ReplaceRepeatedFields)
|
||||
{
|
||||
field.Accessor.Clear(destination);
|
||||
}
|
||||
|
||||
var sourceField = (IList)field.Accessor.GetValue(source);
|
||||
var destinationField = (IList)field.Accessor.GetValue(destination);
|
||||
foreach (var element in sourceField)
|
||||
{
|
||||
destinationField.Add(element);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceField = field.Accessor.GetValue(source);
|
||||
if (field.FieldType == FieldType.Message)
|
||||
{
|
||||
if (options.ReplaceMessageFields)
|
||||
{
|
||||
if (sourceField == null)
|
||||
{
|
||||
field.Accessor.Clear(destination);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.Accessor.SetValue(destination, sourceField);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceField != null)
|
||||
{
|
||||
var sourceByteString = ((IMessage)sourceField).ToByteString();
|
||||
var destinationValue = (IMessage)field.Accessor.GetValue(destination);
|
||||
if (destinationValue != null)
|
||||
{
|
||||
destinationValue.MergeFrom(sourceByteString);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceField != null
|
||||
|| !options.ReplacePrimitiveFields)
|
||||
{
|
||||
field.Accessor.SetValue(destination, sourceField);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.Accessor.Clear(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing helpful workarounds for various platform compatibility
|
||||
/// </summary>
|
||||
internal static class FrameworkPortability
|
||||
{
|
||||
// The value of RegexOptions.Compiled is 8. We can test for the presence at
|
||||
// execution time using Enum.IsDefined, so a single build will do the right thing
|
||||
// on each platform. (RegexOptions.Compiled isn't supported by PCLs.)
|
||||
internal static readonly RegexOptions CompiledRegexWhereAvailable =
|
||||
Enum.IsDefined(typeof(RegexOptions), 8) ? (RegexOptions)8 : RegexOptions.None;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a Protocol Buffers message, supporting
|
||||
/// parsing from <see cref="ParseContext"/> and writing to <see cref="WriteContext"/>.
|
||||
/// </summary>
|
||||
public interface IBufferMessage : IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal implementation of merging data from given parse context into this message.
|
||||
/// Users should never invoke this method directly.
|
||||
/// </summary>
|
||||
void InternalMergeFrom(ref ParseContext ctx);
|
||||
|
||||
/// <summary>
|
||||
/// Internal implementation of writing this message to a given write context.
|
||||
/// Users should never invoke this method directly.
|
||||
/// </summary>
|
||||
void InternalWriteTo(ref WriteContext ctx);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2016 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
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// A message type that has a custom string format for diagnostic purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Calling <see cref="object.ToString"/> on a generated message type normally
|
||||
/// returns the JSON representation. If a message type implements this interface,
|
||||
/// then the <see cref="ToDiagnosticString"/> method will be called instead of the regular
|
||||
/// JSON formatting code, but only when <c>ToString()</c> is called either on the message itself
|
||||
/// or on another message which contains it. This does not affect the normal JSON formatting of
|
||||
/// the message.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For example, if you create a proto message representing a GUID, the internal
|
||||
/// representation may be a <c>bytes</c> field or four <c>fixed32</c> fields. However, when debugging
|
||||
/// it may be more convenient to see a result in the same format as <see cref="System.Guid"/> provides.
|
||||
/// </para>
|
||||
/// <para>This interface extends <see cref="IMessage"/> to avoid it accidentally being implemented
|
||||
/// on types other than messages, where it would not be used by anything in the framework.</para>
|
||||
/// </remarks>
|
||||
public interface ICustomDiagnosticMessage : IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a string representation of this object, for diagnostic purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is called when a message is formatted as part of a <see cref="object.ToString"/>
|
||||
/// call. It does not affect the JSON representation used by <see cref="JsonFormatter"/> other than
|
||||
/// in calls to <see cref="JsonFormatter.ToDiagnosticString(IMessage)"/>. While it is recommended
|
||||
/// that the result is valid JSON, this is never assumed by the Protobuf library.
|
||||
/// </remarks>
|
||||
/// <returns>A string representation of this object, for diagnostic purposes.</returns>
|
||||
string ToDiagnosticString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#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
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic interface for a deeply cloneable type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// All generated messages implement this interface, but so do some non-message types.
|
||||
/// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>,
|
||||
/// it is simpler to keep this as a separate interface.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam>
|
||||
public interface IDeepCloneable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a deep clone of this object.
|
||||
/// </summary>
|
||||
/// <returns>A deep clone of this object.</returns>
|
||||
T Clone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic interface for a Protocol Buffers message containing one or more extensions, where the type parameter is expected to be the same type as the implementation class.
|
||||
/// This interface is experiemental and is subject to change.
|
||||
/// </summary>
|
||||
public interface IExtendableMessage<T> : IMessage<T> where T : IExtendableMessage<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value of the specified extension
|
||||
/// </summary>
|
||||
TValue GetExtension<TValue>(Extension<T, TValue> extension);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified repeated extension or null if the extension isn't registered in this set.
|
||||
/// For a version of this method that never returns null, use <see cref="IExtendableMessage{T}.GetOrInitializeExtension{TValue}(RepeatedExtension{T, TValue})"/>
|
||||
/// </summary>
|
||||
RepeatedField<TValue> GetExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified repeated extension, registering it if it hasn't already been registered.
|
||||
/// </summary>
|
||||
RepeatedField<TValue> GetOrInitializeExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the specified extension
|
||||
/// </summary>
|
||||
void SetExtension<TValue>(Extension<T, TValue> extension, TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the value of the specified extension is set
|
||||
/// </summary>
|
||||
bool HasExtension<TValue>(Extension<T, TValue> extension);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the value of the specified extension
|
||||
/// </summary>
|
||||
void ClearExtension<TValue>(Extension<T, TValue> extension);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the value of the specified repeated extension
|
||||
/// </summary>
|
||||
void ClearExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a Protocol Buffers message, supporting
|
||||
/// basic operations required for serialization.
|
||||
/// </summary>
|
||||
public interface IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges the data from the specified coded input stream with the current message.
|
||||
/// </summary>
|
||||
/// <remarks>See the user guide for precise merge semantics.</remarks>
|
||||
/// <param name="input"></param>
|
||||
void MergeFrom(CodedInputStream input);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the data to the given coded output stream.
|
||||
/// </summary>
|
||||
/// <param name="output">Coded output stream to write the data to. Must not be null.</param>
|
||||
void WriteTo(CodedOutputStream output);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this message in Protocol Buffer wire format, in bytes.
|
||||
/// </summary>
|
||||
/// <returns>The number of bytes required to write this message
|
||||
/// to a coded output stream.</returns>
|
||||
int CalculateSize();
|
||||
|
||||
/// <summary>
|
||||
/// Descriptor for this message. All instances are expected to return the same descriptor,
|
||||
/// and for generated types this will be an explicitly-implemented member, returning the
|
||||
/// same value as the static property declared on the type.
|
||||
/// </summary>
|
||||
MessageDescriptor Descriptor { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic interface for a Protocol Buffers message,
|
||||
/// where the type parameter is expected to be the same type as
|
||||
/// the implementation class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T> where T : IMessage<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges the given message into this one.
|
||||
/// </summary>
|
||||
/// <remarks>See the user guide for precise merge semantics.</remarks>
|
||||
/// <param name="message">The message to merge with this one. Must not be null.</param>
|
||||
void MergeFrom(T message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#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
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when an attempt is made to parse invalid JSON, e.g. using
|
||||
/// a non-string property key, or including a redundant comma. Parsing a protocol buffer
|
||||
/// message represented in JSON using <see cref="JsonParser"/> can throw both this
|
||||
/// exception and <see cref="InvalidProtocolBufferException"/> depending on the situation. This
|
||||
/// exception is only thrown for "pure JSON" errors, whereas <c>InvalidProtocolBufferException</c>
|
||||
/// is thrown when the JSON may be valid in and of itself, but cannot be parsed as a protocol buffer
|
||||
/// message.
|
||||
/// </summary>
|
||||
public sealed class InvalidJsonException : IOException
|
||||
{
|
||||
internal InvalidJsonException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when a protocol message being parsed is invalid in some way,
|
||||
/// e.g. it contains a malformed varint or a negative byte length.
|
||||
/// </summary>
|
||||
public sealed class InvalidProtocolBufferException : IOException
|
||||
{
|
||||
internal InvalidProtocolBufferException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
internal InvalidProtocolBufferException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException MoreDataAvailable()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Completed reading a message while more data was available in the stream.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException TruncatedMessage()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"While parsing a protocol message, the input ended unexpectedly " +
|
||||
"in the middle of a field. This could mean either that the " +
|
||||
"input has been truncated or that an embedded message " +
|
||||
"misreported its own length.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException NegativeSize()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"CodedInputStream encountered an embedded string or message " +
|
||||
"which claimed to have negative size.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException MalformedVarint()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"CodedInputStream encountered a malformed varint.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an exception for an error condition of an invalid tag being encountered.
|
||||
/// </summary>
|
||||
internal static InvalidProtocolBufferException InvalidTag()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message contained an invalid tag (zero).");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException InvalidWireType()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message contained a tag with an invalid wire type.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException InvalidBase64(Exception innerException)
|
||||
{
|
||||
return new InvalidProtocolBufferException("Invalid base64 data", innerException);
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException InvalidEndTag()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message end-group tag did not match expected tag.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException RecursionLimitExceeded()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message had too many levels of nesting. May be malicious. " +
|
||||
"Use CodedInputStream.SetRecursionLimit() to increase the depth limit.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException JsonRecursionLimitExceeded()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message had too many levels of nesting. May be malicious. " +
|
||||
"Use JsonParser.Settings to increase the depth limit.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException SizeLimitExceeded()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Protocol message was too large. May be malicious. " +
|
||||
"Use CodedInputStream.SetSizeLimit() to increase the size limit.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException InvalidMessageStreamTag()
|
||||
{
|
||||
return new InvalidProtocolBufferException(
|
||||
"Stream of protocol messages had invalid tag. Expected tag is length-delimited field 1.");
|
||||
}
|
||||
|
||||
internal static InvalidProtocolBufferException MissingFields()
|
||||
{
|
||||
return new InvalidProtocolBufferException("Message was missing required fields");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,927 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
using LC.Google.Protobuf.WellKnownTypes;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Reflection-based converter from messages to JSON.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Instances of this class are thread-safe, with no mutable state.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is a simple start to get JSON formatting working. As it's reflection-based,
|
||||
/// it's not as quick as baking calls into generated messages - but is a simpler implementation.
|
||||
/// (This code is generally not heavily optimized.)
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class JsonFormatter
|
||||
{
|
||||
internal const string AnyTypeUrlField = "@type";
|
||||
internal const string AnyDiagnosticValueField = "@value";
|
||||
internal const string AnyWellKnownTypeValueField = "value";
|
||||
private const string TypeUrlPrefix = "type.googleapis.com";
|
||||
private const string NameValueSeparator = ": ";
|
||||
private const string PropertySeparator = ", ";
|
||||
|
||||
/// <summary>
|
||||
/// Returns a formatter using the default settings.
|
||||
/// </summary>
|
||||
public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);
|
||||
|
||||
// A JSON formatter which *only* exists
|
||||
private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
|
||||
|
||||
/// <summary>
|
||||
/// The JSON representation of the first 160 characters of Unicode.
|
||||
/// Empty strings are replaced by the static constructor.
|
||||
/// </summary>
|
||||
private static readonly string[] CommonRepresentations = {
|
||||
// C0 (ASCII and derivatives) control characters
|
||||
"\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
|
||||
"\\u0004", "\\u0005", "\\u0006", "\\u0007",
|
||||
"\\b", "\\t", "\\n", "\\u000b",
|
||||
"\\f", "\\r", "\\u000e", "\\u000f",
|
||||
"\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
|
||||
"\\u0014", "\\u0015", "\\u0016", "\\u0017",
|
||||
"\\u0018", "\\u0019", "\\u001a", "\\u001b",
|
||||
"\\u001c", "\\u001d", "\\u001e", "\\u001f",
|
||||
// Escaping of " and \ are required by www.json.org string definition.
|
||||
// Escaping of < and > are required for HTML security.
|
||||
"", "", "\\\"", "", "", "", "", "", // 0x20
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", // 0x30
|
||||
"", "", "", "", "\\u003c", "", "\\u003e", "",
|
||||
"", "", "", "", "", "", "", "", // 0x40
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", // 0x50
|
||||
"", "", "", "", "\\\\", "", "", "",
|
||||
"", "", "", "", "", "", "", "", // 0x60
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", // 0x70
|
||||
"", "", "", "", "", "", "", "\\u007f",
|
||||
// C1 (ISO 8859 and Unicode) extended control characters
|
||||
"\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
|
||||
"\\u0084", "\\u0085", "\\u0086", "\\u0087",
|
||||
"\\u0088", "\\u0089", "\\u008a", "\\u008b",
|
||||
"\\u008c", "\\u008d", "\\u008e", "\\u008f",
|
||||
"\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
|
||||
"\\u0094", "\\u0095", "\\u0096", "\\u0097",
|
||||
"\\u0098", "\\u0099", "\\u009a", "\\u009b",
|
||||
"\\u009c", "\\u009d", "\\u009e", "\\u009f"
|
||||
};
|
||||
|
||||
static JsonFormatter()
|
||||
{
|
||||
for (int i = 0; i < CommonRepresentations.Length; i++)
|
||||
{
|
||||
if (CommonRepresentations[i] == "")
|
||||
{
|
||||
CommonRepresentations[i] = ((char) i).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Settings settings;
|
||||
|
||||
private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new formatted with the given settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
public JsonFormatter(Settings settings)
|
||||
{
|
||||
this.settings = ProtoPreconditions.CheckNotNull(settings, nameof(settings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified message as JSON.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to format.</param>
|
||||
/// <returns>The formatted message.</returns>
|
||||
public string Format(IMessage message)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
Format(message, writer);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified message as JSON.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to format.</param>
|
||||
/// <param name="writer">The TextWriter to write the formatted message to.</param>
|
||||
/// <returns>The formatted message.</returns>
|
||||
public void Format(IMessage message, TextWriter writer)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, nameof(message));
|
||||
ProtoPreconditions.CheckNotNull(writer, nameof(writer));
|
||||
|
||||
if (message.Descriptor.IsWellKnownType)
|
||||
{
|
||||
WriteWellKnownTypeValue(writer, message.Descriptor, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMessage(writer, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a message to JSON for diagnostic purposes with no extra context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
|
||||
/// formatter in its handling of <see cref="Any"/>. As no type registry is available
|
||||
/// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
|
||||
/// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
|
||||
/// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
|
||||
/// </para>
|
||||
/// <para>The value returned by this method is only designed to be used for diagnostic
|
||||
/// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
|
||||
/// by other Protocol Buffer implementations.</para>
|
||||
/// </remarks>
|
||||
/// <param name="message">The message to format for diagnostic purposes.</param>
|
||||
/// <returns>The diagnostic-only JSON representation of the message</returns>
|
||||
public static string ToDiagnosticString(IMessage message)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, nameof(message));
|
||||
return diagnosticFormatter.Format(message);
|
||||
}
|
||||
|
||||
private void WriteMessage(TextWriter writer, IMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
WriteNull(writer);
|
||||
return;
|
||||
}
|
||||
if (DiagnosticOnly)
|
||||
{
|
||||
ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
|
||||
if (customDiagnosticMessage != null)
|
||||
{
|
||||
writer.Write(customDiagnosticMessage.ToDiagnosticString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.Write("{ ");
|
||||
bool writtenFields = WriteMessageFields(writer, message, false);
|
||||
writer.Write(writtenFields ? " }" : "}");
|
||||
}
|
||||
|
||||
private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
|
||||
{
|
||||
var fields = message.Descriptor.Fields;
|
||||
bool first = !assumeFirstFieldWritten;
|
||||
// First non-oneof fields
|
||||
foreach (var field in fields.InFieldNumberOrder())
|
||||
{
|
||||
var accessor = field.Accessor;
|
||||
var value = accessor.GetValue(message);
|
||||
if (!ShouldFormatFieldValue(message, field, value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!first)
|
||||
{
|
||||
writer.Write(PropertySeparator);
|
||||
}
|
||||
|
||||
WriteString(writer, accessor.Descriptor.JsonName);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteValue(writer, value);
|
||||
|
||||
first = false;
|
||||
}
|
||||
return !first;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not a field value should be serialized according to the field,
|
||||
/// its value in the message, and the settings of this formatter.
|
||||
/// </summary>
|
||||
private bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object value) =>
|
||||
field.HasPresence
|
||||
// Fields that support presence *just* use that
|
||||
? field.Accessor.HasValue(message)
|
||||
// Otherwise, format if either we've been asked to format default values, or if it's
|
||||
// not a default value anyway.
|
||||
: settings.FormatDefaultValues || !IsDefaultValue(field, value);
|
||||
|
||||
// Converted from java/core/src/main/java/com/google/protobuf/Descriptors.java
|
||||
internal static string ToJsonName(string name)
|
||||
{
|
||||
StringBuilder result = new StringBuilder(name.Length);
|
||||
bool isNextUpperCase = false;
|
||||
foreach (char ch in name)
|
||||
{
|
||||
if (ch == '_')
|
||||
{
|
||||
isNextUpperCase = true;
|
||||
}
|
||||
else if (isNextUpperCase)
|
||||
{
|
||||
result.Append(char.ToUpperInvariant(ch));
|
||||
isNextUpperCase = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(ch);
|
||||
}
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
internal static string FromJsonName(string name)
|
||||
{
|
||||
StringBuilder result = new StringBuilder(name.Length);
|
||||
foreach (char ch in name)
|
||||
{
|
||||
if (char.IsUpper(ch))
|
||||
{
|
||||
result.Append('_');
|
||||
result.Append(char.ToLowerInvariant(ch));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(ch);
|
||||
}
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static void WriteNull(TextWriter writer)
|
||||
{
|
||||
writer.Write("null");
|
||||
}
|
||||
|
||||
private static bool IsDefaultValue(FieldDescriptor descriptor, object value)
|
||||
{
|
||||
if (descriptor.IsMap)
|
||||
{
|
||||
IDictionary dictionary = (IDictionary) value;
|
||||
return dictionary.Count == 0;
|
||||
}
|
||||
if (descriptor.IsRepeated)
|
||||
{
|
||||
IList list = (IList) value;
|
||||
return list.Count == 0;
|
||||
}
|
||||
switch (descriptor.FieldType)
|
||||
{
|
||||
case FieldType.Bool:
|
||||
return (bool) value == false;
|
||||
case FieldType.Bytes:
|
||||
return (ByteString) value == ByteString.Empty;
|
||||
case FieldType.String:
|
||||
return (string) value == "";
|
||||
case FieldType.Double:
|
||||
return (double) value == 0.0;
|
||||
case FieldType.SInt32:
|
||||
case FieldType.Int32:
|
||||
case FieldType.SFixed32:
|
||||
case FieldType.Enum:
|
||||
return (int) value == 0;
|
||||
case FieldType.Fixed32:
|
||||
case FieldType.UInt32:
|
||||
return (uint) value == 0;
|
||||
case FieldType.Fixed64:
|
||||
case FieldType.UInt64:
|
||||
return (ulong) value == 0;
|
||||
case FieldType.SFixed64:
|
||||
case FieldType.Int64:
|
||||
case FieldType.SInt64:
|
||||
return (long) value == 0;
|
||||
case FieldType.Float:
|
||||
return (float) value == 0f;
|
||||
case FieldType.Message:
|
||||
case FieldType.Group: // Never expect to get this, but...
|
||||
return value == null;
|
||||
default:
|
||||
throw new ArgumentException("Invalid field type");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single value to the given writer as JSON. Only types understood by
|
||||
/// Protocol Buffers can be written in this way. This method is only exposed for
|
||||
/// advanced use cases; most users should be using <see cref="Format(IMessage)"/>
|
||||
/// or <see cref="Format(IMessage, TextWriter)"/>.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write the value to. Must not be null.</param>
|
||||
/// <param name="value">The value to write. May be null.</param>
|
||||
public void WriteValue(TextWriter writer, object value)
|
||||
{
|
||||
if (value == null || value is NullValue)
|
||||
{
|
||||
WriteNull(writer);
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
writer.Write((bool)value ? "true" : "false");
|
||||
}
|
||||
else if (value is ByteString)
|
||||
{
|
||||
// Nothing in Base64 needs escaping
|
||||
writer.Write('"');
|
||||
writer.Write(((ByteString)value).ToBase64());
|
||||
writer.Write('"');
|
||||
}
|
||||
else if (value is string)
|
||||
{
|
||||
WriteString(writer, (string)value);
|
||||
}
|
||||
else if (value is IDictionary)
|
||||
{
|
||||
WriteDictionary(writer, (IDictionary)value);
|
||||
}
|
||||
else if (value is IList)
|
||||
{
|
||||
WriteList(writer, (IList)value);
|
||||
}
|
||||
else if (value is int || value is uint)
|
||||
{
|
||||
IFormattable formattable = (IFormattable) value;
|
||||
writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (value is long || value is ulong)
|
||||
{
|
||||
writer.Write('"');
|
||||
IFormattable formattable = (IFormattable) value;
|
||||
writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
|
||||
writer.Write('"');
|
||||
}
|
||||
else if (value is System.Enum)
|
||||
{
|
||||
if (settings.FormatEnumsAsIntegers)
|
||||
{
|
||||
WriteValue(writer, (int)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = OriginalEnumValueHelper.GetOriginalName(value);
|
||||
if (name != null)
|
||||
{
|
||||
WriteString(writer, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteValue(writer, (int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (value is float || value is double)
|
||||
{
|
||||
string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
|
||||
if (text == "NaN" || text == "Infinity" || text == "-Infinity")
|
||||
{
|
||||
writer.Write('"');
|
||||
writer.Write(text);
|
||||
writer.Write('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(text);
|
||||
}
|
||||
}
|
||||
else if (value is IMessage)
|
||||
{
|
||||
Format((IMessage)value, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unable to format value of type " + value.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Central interception point for well-known type formatting. Any well-known types which
|
||||
/// don't need special handling can fall back to WriteMessage. We avoid assuming that the
|
||||
/// values are using the embedded well-known types, in order to allow for dynamic messages
|
||||
/// in the future.
|
||||
/// </summary>
|
||||
private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
|
||||
{
|
||||
// Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
|
||||
// this would do the right thing.
|
||||
if (value == null)
|
||||
{
|
||||
WriteNull(writer);
|
||||
return;
|
||||
}
|
||||
// For wrapper types, the value will either be the (possibly boxed) "native" value,
|
||||
// or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself).
|
||||
// If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
|
||||
// and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
|
||||
// WriteValue will do the right thing.)
|
||||
if (descriptor.IsWrapperType)
|
||||
{
|
||||
if (value is IMessage)
|
||||
{
|
||||
var message = (IMessage) value;
|
||||
value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
|
||||
}
|
||||
WriteValue(writer, value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == Timestamp.Descriptor.FullName)
|
||||
{
|
||||
WriteTimestamp(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == Duration.Descriptor.FullName)
|
||||
{
|
||||
WriteDuration(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == FieldMask.Descriptor.FullName)
|
||||
{
|
||||
WriteFieldMask(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == Struct.Descriptor.FullName)
|
||||
{
|
||||
WriteStruct(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == ListValue.Descriptor.FullName)
|
||||
{
|
||||
var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
|
||||
WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == Value.Descriptor.FullName)
|
||||
{
|
||||
WriteStructFieldValue(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
if (descriptor.FullName == Any.Descriptor.FullName)
|
||||
{
|
||||
WriteAny(writer, (IMessage)value);
|
||||
return;
|
||||
}
|
||||
WriteMessage(writer, (IMessage)value);
|
||||
}
|
||||
|
||||
private void WriteTimestamp(TextWriter writer, IMessage value)
|
||||
{
|
||||
// TODO: In the common case where this *is* using the built-in Timestamp type, we could
|
||||
// avoid all the reflection at this point, by casting to Timestamp. In the interests of
|
||||
// avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
|
||||
// it still works in that case.
|
||||
int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
|
||||
long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
|
||||
writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
|
||||
}
|
||||
|
||||
private void WriteDuration(TextWriter writer, IMessage value)
|
||||
{
|
||||
// TODO: Same as for WriteTimestamp
|
||||
int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
|
||||
long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
|
||||
writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
|
||||
}
|
||||
|
||||
private void WriteFieldMask(TextWriter writer, IMessage value)
|
||||
{
|
||||
var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
|
||||
writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
|
||||
}
|
||||
|
||||
private void WriteAny(TextWriter writer, IMessage value)
|
||||
{
|
||||
if (DiagnosticOnly)
|
||||
{
|
||||
WriteDiagnosticOnlyAny(writer, value);
|
||||
return;
|
||||
}
|
||||
|
||||
string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
|
||||
ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
|
||||
string typeName = Any.GetTypeName(typeUrl);
|
||||
MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
|
||||
if (descriptor == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
|
||||
}
|
||||
IMessage message = descriptor.Parser.ParseFrom(data);
|
||||
writer.Write("{ ");
|
||||
WriteString(writer, AnyTypeUrlField);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteString(writer, typeUrl);
|
||||
|
||||
if (descriptor.IsWellKnownType)
|
||||
{
|
||||
writer.Write(PropertySeparator);
|
||||
WriteString(writer, AnyWellKnownTypeValueField);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteWellKnownTypeValue(writer, descriptor, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMessageFields(writer, message, true);
|
||||
}
|
||||
writer.Write(" }");
|
||||
}
|
||||
|
||||
private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
|
||||
{
|
||||
string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
|
||||
ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
|
||||
writer.Write("{ ");
|
||||
WriteString(writer, AnyTypeUrlField);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteString(writer, typeUrl);
|
||||
writer.Write(PropertySeparator);
|
||||
WriteString(writer, AnyDiagnosticValueField);
|
||||
writer.Write(NameValueSeparator);
|
||||
writer.Write('"');
|
||||
writer.Write(data.ToBase64());
|
||||
writer.Write('"');
|
||||
writer.Write(" }");
|
||||
}
|
||||
|
||||
private void WriteStruct(TextWriter writer, IMessage message)
|
||||
{
|
||||
writer.Write("{ ");
|
||||
IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
|
||||
bool first = true;
|
||||
foreach (DictionaryEntry entry in fields)
|
||||
{
|
||||
string key = (string) entry.Key;
|
||||
IMessage value = (IMessage) entry.Value;
|
||||
if (string.IsNullOrEmpty(key) || value == null)
|
||||
{
|
||||
throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
|
||||
}
|
||||
|
||||
if (!first)
|
||||
{
|
||||
writer.Write(PropertySeparator);
|
||||
}
|
||||
WriteString(writer, key);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteStructFieldValue(writer, value);
|
||||
first = false;
|
||||
}
|
||||
writer.Write(first ? "}" : " }");
|
||||
}
|
||||
|
||||
private void WriteStructFieldValue(TextWriter writer, IMessage message)
|
||||
{
|
||||
var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
|
||||
if (specifiedField == null)
|
||||
{
|
||||
throw new InvalidOperationException("Value message must contain a value for the oneof.");
|
||||
}
|
||||
|
||||
object value = specifiedField.Accessor.GetValue(message);
|
||||
|
||||
switch (specifiedField.FieldNumber)
|
||||
{
|
||||
case Value.BoolValueFieldNumber:
|
||||
case Value.StringValueFieldNumber:
|
||||
case Value.NumberValueFieldNumber:
|
||||
WriteValue(writer, value);
|
||||
return;
|
||||
case Value.StructValueFieldNumber:
|
||||
case Value.ListValueFieldNumber:
|
||||
// Structs and ListValues are nested messages, and already well-known types.
|
||||
var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
|
||||
WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
|
||||
return;
|
||||
case Value.NullValueFieldNumber:
|
||||
WriteNull(writer);
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteList(TextWriter writer, IList list)
|
||||
{
|
||||
writer.Write("[ ");
|
||||
bool first = true;
|
||||
foreach (var value in list)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
writer.Write(PropertySeparator);
|
||||
}
|
||||
WriteValue(writer, value);
|
||||
first = false;
|
||||
}
|
||||
writer.Write(first ? "]" : " ]");
|
||||
}
|
||||
|
||||
internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
|
||||
{
|
||||
writer.Write("{ ");
|
||||
bool first = true;
|
||||
// This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
|
||||
foreach (DictionaryEntry pair in dictionary)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
writer.Write(PropertySeparator);
|
||||
}
|
||||
string keyText;
|
||||
if (pair.Key is string)
|
||||
{
|
||||
keyText = (string) pair.Key;
|
||||
}
|
||||
else if (pair.Key is bool)
|
||||
{
|
||||
keyText = (bool) pair.Key ? "true" : "false";
|
||||
}
|
||||
else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
|
||||
{
|
||||
keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pair.Key == null)
|
||||
{
|
||||
throw new ArgumentException("Dictionary has entry with null key");
|
||||
}
|
||||
throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
|
||||
}
|
||||
WriteString(writer, keyText);
|
||||
writer.Write(NameValueSeparator);
|
||||
WriteValue(writer, pair.Value);
|
||||
first = false;
|
||||
}
|
||||
writer.Write(first ? "}" : " }");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc.
|
||||
/// </remarks>
|
||||
internal static void WriteString(TextWriter writer, string text)
|
||||
{
|
||||
writer.Write('"');
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
char c = text[i];
|
||||
if (c < 0xa0)
|
||||
{
|
||||
writer.Write(CommonRepresentations[c]);
|
||||
continue;
|
||||
}
|
||||
if (char.IsHighSurrogate(c))
|
||||
{
|
||||
// Encountered first part of a surrogate pair.
|
||||
// Check that we have the whole pair, and encode both parts as hex.
|
||||
i++;
|
||||
if (i == text.Length || !char.IsLowSurrogate(text[i]))
|
||||
{
|
||||
throw new ArgumentException("String contains low surrogate not followed by high surrogate");
|
||||
}
|
||||
HexEncodeUtf16CodeUnit(writer, c);
|
||||
HexEncodeUtf16CodeUnit(writer, text[i]);
|
||||
continue;
|
||||
}
|
||||
else if (char.IsLowSurrogate(c))
|
||||
{
|
||||
throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
|
||||
}
|
||||
switch ((uint) c)
|
||||
{
|
||||
// These are not required by json spec
|
||||
// but used to prevent security bugs in javascript.
|
||||
case 0xfeff: // Zero width no-break space
|
||||
case 0xfff9: // Interlinear annotation anchor
|
||||
case 0xfffa: // Interlinear annotation separator
|
||||
case 0xfffb: // Interlinear annotation terminator
|
||||
|
||||
case 0x00ad: // Soft-hyphen
|
||||
case 0x06dd: // Arabic end of ayah
|
||||
case 0x070f: // Syriac abbreviation mark
|
||||
case 0x17b4: // Khmer vowel inherent Aq
|
||||
case 0x17b5: // Khmer vowel inherent Aa
|
||||
HexEncodeUtf16CodeUnit(writer, c);
|
||||
break;
|
||||
|
||||
default:
|
||||
if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
|
||||
(c >= 0x200b && c <= 0x200f) || // Zero width etc.
|
||||
(c >= 0x2028 && c <= 0x202e) || // Separators etc.
|
||||
(c >= 0x2060 && c <= 0x2064) || // Invisible etc.
|
||||
(c >= 0x206a && c <= 0x206f))
|
||||
{
|
||||
HexEncodeUtf16CodeUnit(writer, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No handling of surrogates here - that's done earlier
|
||||
writer.Write(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
writer.Write('"');
|
||||
}
|
||||
|
||||
private const string Hex = "0123456789abcdef";
|
||||
private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
|
||||
{
|
||||
writer.Write("\\u");
|
||||
writer.Write(Hex[(c >> 12) & 0xf]);
|
||||
writer.Write(Hex[(c >> 8) & 0xf]);
|
||||
writer.Write(Hex[(c >> 4) & 0xf]);
|
||||
writer.Write(Hex[(c >> 0) & 0xf]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings controlling JSON formatting.
|
||||
/// </summary>
|
||||
public sealed class Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Default settings, as used by <see cref="JsonFormatter.Default"/>
|
||||
/// </summary>
|
||||
public static Settings Default { get; }
|
||||
|
||||
// Workaround for the Mono compiler complaining about XML comments not being on
|
||||
// valid language elements.
|
||||
static Settings()
|
||||
{
|
||||
Default = new Settings(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether fields which would otherwise not be included in the formatted data
|
||||
/// should be formatted even when the value is not present, or has the default value.
|
||||
/// This option only affects fields which don't support "presence" (e.g.
|
||||
/// singular non-optional proto3 primitive fields).
|
||||
/// </summary>
|
||||
public bool FormatDefaultValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type registry used to format <see cref="Any"/> messages.
|
||||
/// </summary>
|
||||
public TypeRegistry TypeRegistry { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to format enums as ints. Defaults to false.
|
||||
/// </summary>
|
||||
public bool FormatEnumsAsIntegers { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values
|
||||
/// and an empty type registry.
|
||||
/// </summary>
|
||||
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
|
||||
public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values
|
||||
/// and type registry.
|
||||
/// </summary>
|
||||
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
|
||||
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
|
||||
public Settings(bool formatDefaultValues, TypeRegistry typeRegistry) : this(formatDefaultValues, typeRegistry, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
|
||||
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages. TypeRegistry.Empty will be used if it is null.</param>
|
||||
/// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
|
||||
private Settings(bool formatDefaultValues,
|
||||
TypeRegistry typeRegistry,
|
||||
bool formatEnumsAsIntegers)
|
||||
{
|
||||
FormatDefaultValues = formatDefaultValues;
|
||||
TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
|
||||
FormatEnumsAsIntegers = formatEnumsAsIntegers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values and the current settings.
|
||||
/// </summary>
|
||||
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
|
||||
public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified type registry and the current settings.
|
||||
/// </summary>
|
||||
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
|
||||
public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Settings"/> object with the specified enums formatting option and the current settings.
|
||||
/// </summary>
|
||||
/// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
|
||||
public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers);
|
||||
}
|
||||
|
||||
// Effectively a cache of mapping from enum values to the original name as specified in the proto file,
|
||||
// fetched by reflection.
|
||||
// The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues.
|
||||
private static class OriginalEnumValueHelper
|
||||
{
|
||||
// TODO: In the future we might want to use ConcurrentDictionary, at the point where all
|
||||
// the platforms we target have it.
|
||||
private static readonly Dictionary<System.Type, Dictionary<object, string>> dictionaries
|
||||
= new Dictionary<System.Type, Dictionary<object, string>>();
|
||||
|
||||
internal static string GetOriginalName(object value)
|
||||
{
|
||||
var enumType = value.GetType();
|
||||
Dictionary<object, string> nameMapping;
|
||||
lock (dictionaries)
|
||||
{
|
||||
if (!dictionaries.TryGetValue(enumType, out nameMapping))
|
||||
{
|
||||
nameMapping = GetNameMapping(enumType);
|
||||
dictionaries[enumType] = nameMapping;
|
||||
}
|
||||
}
|
||||
|
||||
string originalName;
|
||||
// If this returns false, originalName will be null, which is what we want.
|
||||
nameMapping.TryGetValue(value, out originalName);
|
||||
return originalName;
|
||||
}
|
||||
|
||||
#if NET35
|
||||
// TODO: Consider adding functionality to TypeExtensions to avoid this difference.
|
||||
private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
|
||||
enumType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
|
||||
.FirstOrDefault() as OriginalNameAttribute)
|
||||
?.PreferredAlias ?? true)
|
||||
.ToDictionary(f => f.GetValue(null),
|
||||
f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
|
||||
.FirstOrDefault() as OriginalNameAttribute)
|
||||
// If the attribute hasn't been applied, fall back to the name of the field.
|
||||
?.Name ?? f.Name);
|
||||
#else
|
||||
private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
|
||||
enumType.GetTypeInfo().DeclaredFields
|
||||
.Where(f => f.IsStatic)
|
||||
.Where(f => f.GetCustomAttributes<OriginalNameAttribute>()
|
||||
.FirstOrDefault()?.PreferredAlias ?? true)
|
||||
.ToDictionary(f => f.GetValue(null),
|
||||
f => f.GetCustomAttributes<OriginalNameAttribute>()
|
||||
.FirstOrDefault()
|
||||
// If the attribute hasn't been applied, fall back to the name of the field.
|
||||
?.Name ?? f.Name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,166 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
internal sealed class JsonToken : IEquatable<JsonToken>
|
||||
{
|
||||
// Tokens with no value can be reused.
|
||||
private static readonly JsonToken _true = new JsonToken(TokenType.True);
|
||||
private static readonly JsonToken _false = new JsonToken(TokenType.False);
|
||||
private static readonly JsonToken _null = new JsonToken(TokenType.Null);
|
||||
private static readonly JsonToken startObject = new JsonToken(TokenType.StartObject);
|
||||
private static readonly JsonToken endObject = new JsonToken(TokenType.EndObject);
|
||||
private static readonly JsonToken startArray = new JsonToken(TokenType.StartArray);
|
||||
private static readonly JsonToken endArray = new JsonToken(TokenType.EndArray);
|
||||
private static readonly JsonToken endDocument = new JsonToken(TokenType.EndDocument);
|
||||
|
||||
internal static JsonToken Null { get { return _null; } }
|
||||
internal static JsonToken False { get { return _false; } }
|
||||
internal static JsonToken True { get { return _true; } }
|
||||
internal static JsonToken StartObject{ get { return startObject; } }
|
||||
internal static JsonToken EndObject { get { return endObject; } }
|
||||
internal static JsonToken StartArray { get { return startArray; } }
|
||||
internal static JsonToken EndArray { get { return endArray; } }
|
||||
internal static JsonToken EndDocument { get { return endDocument; } }
|
||||
|
||||
internal static JsonToken Name(string name)
|
||||
{
|
||||
return new JsonToken(TokenType.Name, stringValue: name);
|
||||
}
|
||||
|
||||
internal static JsonToken Value(string value)
|
||||
{
|
||||
return new JsonToken(TokenType.StringValue, stringValue: value);
|
||||
}
|
||||
|
||||
internal static JsonToken Value(double value)
|
||||
{
|
||||
return new JsonToken(TokenType.Number, numberValue: value);
|
||||
}
|
||||
|
||||
internal enum TokenType
|
||||
{
|
||||
Null,
|
||||
False,
|
||||
True,
|
||||
StringValue,
|
||||
Number,
|
||||
Name,
|
||||
StartObject,
|
||||
EndObject,
|
||||
StartArray,
|
||||
EndArray,
|
||||
EndDocument
|
||||
}
|
||||
|
||||
// A value is a string, number, array, object, null, true or false
|
||||
// Arrays and objects have start/end
|
||||
// A document consists of a value
|
||||
// Objects are name/value sequences.
|
||||
|
||||
private readonly TokenType type;
|
||||
private readonly string stringValue;
|
||||
private readonly double numberValue;
|
||||
|
||||
internal TokenType Type { get { return type; } }
|
||||
internal string StringValue { get { return stringValue; } }
|
||||
internal double NumberValue { get { return numberValue; } }
|
||||
|
||||
private JsonToken(TokenType type, string stringValue = null, double numberValue = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.stringValue = stringValue;
|
||||
this.numberValue = numberValue;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as JsonToken);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + (int) type;
|
||||
hash = hash * 31 + stringValue == null ? 0 : stringValue.GetHashCode();
|
||||
hash = hash * 31 + numberValue.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
return "null";
|
||||
case TokenType.True:
|
||||
return "true";
|
||||
case TokenType.False:
|
||||
return "false";
|
||||
case TokenType.Name:
|
||||
return "name (" + stringValue + ")";
|
||||
case TokenType.StringValue:
|
||||
return "value (" + stringValue + ")";
|
||||
case TokenType.Number:
|
||||
return "number (" + numberValue + ")";
|
||||
case TokenType.StartObject:
|
||||
return "start-object";
|
||||
case TokenType.EndObject:
|
||||
return "end-object";
|
||||
case TokenType.StartArray:
|
||||
return "start-array";
|
||||
case TokenType.EndArray:
|
||||
return "end-array";
|
||||
case TokenType.EndDocument:
|
||||
return "end-document";
|
||||
default:
|
||||
throw new InvalidOperationException("Token is of unknown type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(JsonToken other)
|
||||
{
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Note use of other.numberValue.Equals rather than ==, so that NaN compares appropriately.
|
||||
return other.type == type && other.stringValue == stringValue && other.numberValue.Equals(numberValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,766 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple but strict JSON tokenizer, rigidly following RFC 7159.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This tokenizer is stateful, and only returns "useful" tokens - names, values etc.
|
||||
/// It does not create tokens for the separator between names and values, or for the comma
|
||||
/// between values. It validates the token stream as it goes - so callers can assume that the
|
||||
/// tokens it produces are appropriate. For example, it would never produce "start object, end array."
|
||||
/// </para>
|
||||
/// <para>Implementation details: the base class handles single token push-back and </para>
|
||||
/// <para>Not thread-safe.</para>
|
||||
/// </remarks>
|
||||
internal abstract class JsonTokenizer
|
||||
{
|
||||
private JsonToken bufferedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a tokenizer that reads from the given text reader.
|
||||
/// </summary>
|
||||
internal static JsonTokenizer FromTextReader(TextReader reader)
|
||||
{
|
||||
return new JsonTextTokenizer(reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a tokenizer that first replays the given list of tokens, then continues reading
|
||||
/// from another tokenizer. Note that if the returned tokenizer is "pushed back", that does not push back
|
||||
/// on the continuation tokenizer, or vice versa. Care should be taken when using this method - it was
|
||||
/// created for the sake of Any parsing.
|
||||
/// </summary>
|
||||
internal static JsonTokenizer FromReplayedTokens(IList<JsonToken> tokens, JsonTokenizer continuation)
|
||||
{
|
||||
return new JsonReplayTokenizer(tokens, continuation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the depth of the stack, purely in objects (not collections).
|
||||
/// Informally, this is the number of remaining unclosed '{' characters we have.
|
||||
/// </summary>
|
||||
internal int ObjectDepth { get; private set; }
|
||||
|
||||
// TODO: Why do we allow a different token to be pushed back? It might be better to always remember the previous
|
||||
// token returned, and allow a parameterless Rewind() method (which could only be called once, just like the current PushBack).
|
||||
internal void PushBack(JsonToken token)
|
||||
{
|
||||
if (bufferedToken != null)
|
||||
{
|
||||
throw new InvalidOperationException("Can't push back twice");
|
||||
}
|
||||
bufferedToken = token;
|
||||
if (token.Type == JsonToken.TokenType.StartObject)
|
||||
{
|
||||
ObjectDepth--;
|
||||
}
|
||||
else if (token.Type == JsonToken.TokenType.EndObject)
|
||||
{
|
||||
ObjectDepth++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next JSON token in the stream. An EndDocument token is returned to indicate the end of the stream,
|
||||
/// after which point <c>Next()</c> should not be called again.
|
||||
/// </summary>
|
||||
/// <remarks>This implementation provides single-token buffering, and calls <see cref="NextImpl"/> if there is no buffered token.</remarks>
|
||||
/// <returns>The next token in the stream. This is never null.</returns>
|
||||
/// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception>
|
||||
/// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception>
|
||||
internal JsonToken Next()
|
||||
{
|
||||
JsonToken tokenToReturn;
|
||||
if (bufferedToken != null)
|
||||
{
|
||||
tokenToReturn = bufferedToken;
|
||||
bufferedToken = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenToReturn = NextImpl();
|
||||
}
|
||||
if (tokenToReturn.Type == JsonToken.TokenType.StartObject)
|
||||
{
|
||||
ObjectDepth++;
|
||||
}
|
||||
else if (tokenToReturn.Type == JsonToken.TokenType.EndObject)
|
||||
{
|
||||
ObjectDepth--;
|
||||
}
|
||||
return tokenToReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next JSON token in the stream, when requested by the base class. (The <see cref="Next"/> method delegates
|
||||
/// to this if it doesn't have a buffered token.)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception>
|
||||
/// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception>
|
||||
protected abstract JsonToken NextImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Skips the value we're about to read. This must only be called immediately after reading a property name.
|
||||
/// If the value is an object or an array, the complete object/array is skipped.
|
||||
/// </summary>
|
||||
internal void SkipValue()
|
||||
{
|
||||
// We'll assume that Next() makes sure that the end objects and end arrays are all valid.
|
||||
// All we care about is the total nesting depth we need to close.
|
||||
int depth = 0;
|
||||
|
||||
// do/while rather than while loop so that we read at least one token.
|
||||
do
|
||||
{
|
||||
var token = Next();
|
||||
switch (token.Type)
|
||||
{
|
||||
case JsonToken.TokenType.EndArray:
|
||||
case JsonToken.TokenType.EndObject:
|
||||
depth--;
|
||||
break;
|
||||
case JsonToken.TokenType.StartArray:
|
||||
case JsonToken.TokenType.StartObject:
|
||||
depth++;
|
||||
break;
|
||||
}
|
||||
} while (depth != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tokenizer which first exhausts a list of tokens, then consults another tokenizer.
|
||||
/// </summary>
|
||||
private class JsonReplayTokenizer : JsonTokenizer
|
||||
{
|
||||
private readonly IList<JsonToken> tokens;
|
||||
private readonly JsonTokenizer nextTokenizer;
|
||||
private int nextTokenIndex;
|
||||
|
||||
internal JsonReplayTokenizer(IList<JsonToken> tokens, JsonTokenizer nextTokenizer)
|
||||
{
|
||||
this.tokens = tokens;
|
||||
this.nextTokenizer = nextTokenizer;
|
||||
}
|
||||
|
||||
// FIXME: Object depth not maintained...
|
||||
protected override JsonToken NextImpl()
|
||||
{
|
||||
if (nextTokenIndex >= tokens.Count)
|
||||
{
|
||||
return nextTokenizer.Next();
|
||||
}
|
||||
return tokens[nextTokenIndex++];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tokenizer which does all the *real* work of parsing JSON.
|
||||
/// </summary>
|
||||
private sealed class JsonTextTokenizer : JsonTokenizer
|
||||
{
|
||||
// The set of states in which a value is valid next token.
|
||||
private static readonly State ValueStates = State.ArrayStart | State.ArrayAfterComma | State.ObjectAfterColon | State.StartOfDocument;
|
||||
|
||||
private readonly Stack<ContainerType> containerStack = new Stack<ContainerType>();
|
||||
private readonly PushBackReader reader;
|
||||
private State state;
|
||||
|
||||
internal JsonTextTokenizer(TextReader reader)
|
||||
{
|
||||
this.reader = new PushBackReader(reader);
|
||||
state = State.StartOfDocument;
|
||||
containerStack.Push(ContainerType.Document);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This method essentially just loops through characters skipping whitespace, validating and
|
||||
/// changing state (e.g. from ObjectBeforeColon to ObjectAfterColon)
|
||||
/// until it reaches something which will be a genuine token (e.g. a start object, or a value) at which point
|
||||
/// it returns the token. Although the method is large, it would be relatively hard to break down further... most
|
||||
/// of it is the large switch statement, which sometimes returns and sometimes doesn't.
|
||||
/// </remarks>
|
||||
protected override JsonToken NextImpl()
|
||||
{
|
||||
if (state == State.ReaderExhausted)
|
||||
{
|
||||
throw new InvalidOperationException("Next() called after end of document");
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
var next = reader.Read();
|
||||
if (next == null)
|
||||
{
|
||||
ValidateState(State.ExpectedEndOfDocument, "Unexpected end of document in state: ");
|
||||
state = State.ReaderExhausted;
|
||||
return JsonToken.EndDocument;
|
||||
}
|
||||
switch (next.Value)
|
||||
{
|
||||
// Skip whitespace between tokens
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
break;
|
||||
case ':':
|
||||
ValidateState(State.ObjectBeforeColon, "Invalid state to read a colon: ");
|
||||
state = State.ObjectAfterColon;
|
||||
break;
|
||||
case ',':
|
||||
ValidateState(State.ObjectAfterProperty | State.ArrayAfterValue, "Invalid state to read a comma: ");
|
||||
state = state == State.ObjectAfterProperty ? State.ObjectAfterComma : State.ArrayAfterComma;
|
||||
break;
|
||||
case '"':
|
||||
string stringValue = ReadString();
|
||||
if ((state & (State.ObjectStart | State.ObjectAfterComma)) != 0)
|
||||
{
|
||||
state = State.ObjectBeforeColon;
|
||||
return JsonToken.Name(stringValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
ValidateAndModifyStateForValue("Invalid state to read a double quote: ");
|
||||
return JsonToken.Value(stringValue);
|
||||
}
|
||||
case '{':
|
||||
ValidateState(ValueStates, "Invalid state to read an open brace: ");
|
||||
state = State.ObjectStart;
|
||||
containerStack.Push(ContainerType.Object);
|
||||
return JsonToken.StartObject;
|
||||
case '}':
|
||||
ValidateState(State.ObjectAfterProperty | State.ObjectStart, "Invalid state to read a close brace: ");
|
||||
PopContainer();
|
||||
return JsonToken.EndObject;
|
||||
case '[':
|
||||
ValidateState(ValueStates, "Invalid state to read an open square bracket: ");
|
||||
state = State.ArrayStart;
|
||||
containerStack.Push(ContainerType.Array);
|
||||
return JsonToken.StartArray;
|
||||
case ']':
|
||||
ValidateState(State.ArrayAfterValue | State.ArrayStart, "Invalid state to read a close square bracket: ");
|
||||
PopContainer();
|
||||
return JsonToken.EndArray;
|
||||
case 'n': // Start of null
|
||||
ConsumeLiteral("null");
|
||||
ValidateAndModifyStateForValue("Invalid state to read a null literal: ");
|
||||
return JsonToken.Null;
|
||||
case 't': // Start of true
|
||||
ConsumeLiteral("true");
|
||||
ValidateAndModifyStateForValue("Invalid state to read a true literal: ");
|
||||
return JsonToken.True;
|
||||
case 'f': // Start of false
|
||||
ConsumeLiteral("false");
|
||||
ValidateAndModifyStateForValue("Invalid state to read a false literal: ");
|
||||
return JsonToken.False;
|
||||
case '-': // Start of a number
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
double number = ReadNumber(next.Value);
|
||||
ValidateAndModifyStateForValue("Invalid state to read a number token: ");
|
||||
return JsonToken.Value(number);
|
||||
default:
|
||||
throw new InvalidJsonException("Invalid first character of token: " + next.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateState(State validStates, string errorPrefix)
|
||||
{
|
||||
if ((validStates & state) == 0)
|
||||
{
|
||||
throw reader.CreateException(errorPrefix + state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string token. It is assumed that the opening " has already been read.
|
||||
/// </summary>
|
||||
private string ReadString()
|
||||
{
|
||||
var value = new StringBuilder();
|
||||
bool haveHighSurrogate = false;
|
||||
while (true)
|
||||
{
|
||||
char c = reader.ReadOrFail("Unexpected end of text while reading string");
|
||||
if (c < ' ')
|
||||
{
|
||||
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c));
|
||||
}
|
||||
if (c == '"')
|
||||
{
|
||||
if (haveHighSurrogate)
|
||||
{
|
||||
throw reader.CreateException("Invalid use of surrogate pair code units");
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
if (c == '\\')
|
||||
{
|
||||
c = ReadEscapedCharacter();
|
||||
}
|
||||
// TODO: Consider only allowing surrogate pairs that are either both escaped,
|
||||
// or both not escaped. It would be a very odd text stream that contained a "lone" high surrogate
|
||||
// followed by an escaped low surrogate or vice versa... and that couldn't even be represented in UTF-8.
|
||||
if (haveHighSurrogate != char.IsLowSurrogate(c))
|
||||
{
|
||||
throw reader.CreateException("Invalid use of surrogate pair code units");
|
||||
}
|
||||
haveHighSurrogate = char.IsHighSurrogate(c);
|
||||
value.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an escaped character. It is assumed that the leading backslash has already been read.
|
||||
/// </summary>
|
||||
private char ReadEscapedCharacter()
|
||||
{
|
||||
char c = reader.ReadOrFail("Unexpected end of text while reading character escape sequence");
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
return '\n';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'b':
|
||||
return '\b';
|
||||
case 'f':
|
||||
return '\f';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 't':
|
||||
return '\t';
|
||||
case '"':
|
||||
return '"';
|
||||
case '/':
|
||||
return '/';
|
||||
case 'u':
|
||||
return ReadUnicodeEscape();
|
||||
default:
|
||||
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an escaped Unicode 4-nybble hex sequence. It is assumed that the leading \u has already been read.
|
||||
/// </summary>
|
||||
private char ReadUnicodeEscape()
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
char c = reader.ReadOrFail("Unexpected end of text while reading Unicode escape sequence");
|
||||
int nybble;
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
nybble = c - '0';
|
||||
}
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
nybble = c - 'a' + 10;
|
||||
}
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
nybble = c - 'A' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
|
||||
}
|
||||
result = (result << 4) + nybble;
|
||||
}
|
||||
return (char) result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consumes a text-only literal, throwing an exception if the read text doesn't match it.
|
||||
/// It is assumed that the first letter of the literal has already been read.
|
||||
/// </summary>
|
||||
private void ConsumeLiteral(string text)
|
||||
{
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
{
|
||||
char? next = reader.Read();
|
||||
if (next == null)
|
||||
{
|
||||
throw reader.CreateException("Unexpected end of text while reading literal token " + text);
|
||||
}
|
||||
if (next.Value != text[i])
|
||||
{
|
||||
throw reader.CreateException("Unexpected character while reading literal token " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double ReadNumber(char initialCharacter)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (initialCharacter == '-')
|
||||
{
|
||||
builder.Append("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.PushBack(initialCharacter);
|
||||
}
|
||||
// Each method returns the character it read that doesn't belong in that part,
|
||||
// so we know what to do next, including pushing the character back at the end.
|
||||
// null is returned for "end of text".
|
||||
char? next = ReadInt(builder);
|
||||
if (next == '.')
|
||||
{
|
||||
next = ReadFrac(builder);
|
||||
}
|
||||
if (next == 'e' || next == 'E')
|
||||
{
|
||||
next = ReadExp(builder);
|
||||
}
|
||||
// If we read a character which wasn't part of the number, push it back so we can read it again
|
||||
// to parse the next token.
|
||||
if (next != null)
|
||||
{
|
||||
reader.PushBack(next.Value);
|
||||
}
|
||||
|
||||
// TODO: What exception should we throw if the value can't be represented as a double?
|
||||
try
|
||||
{
|
||||
return double.Parse(builder.ToString(),
|
||||
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
throw reader.CreateException("Numeric value out of range: " + builder);
|
||||
}
|
||||
}
|
||||
|
||||
private char? ReadInt(StringBuilder builder)
|
||||
{
|
||||
char first = reader.ReadOrFail("Invalid numeric literal");
|
||||
if (first < '0' || first > '9')
|
||||
{
|
||||
throw reader.CreateException("Invalid numeric literal");
|
||||
}
|
||||
builder.Append(first);
|
||||
int digitCount;
|
||||
char? next = ConsumeDigits(builder, out digitCount);
|
||||
if (first == '0' && digitCount != 0)
|
||||
{
|
||||
throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value.");
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private char? ReadFrac(StringBuilder builder)
|
||||
{
|
||||
builder.Append('.'); // Already consumed this
|
||||
int digitCount;
|
||||
char? next = ConsumeDigits(builder, out digitCount);
|
||||
if (digitCount == 0)
|
||||
{
|
||||
throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits");
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private char? ReadExp(StringBuilder builder)
|
||||
{
|
||||
builder.Append('E'); // Already consumed this (or 'e')
|
||||
char? next = reader.Read();
|
||||
if (next == null)
|
||||
{
|
||||
throw reader.CreateException("Invalid numeric literal: exponent with no trailing digits");
|
||||
}
|
||||
if (next == '-' || next == '+')
|
||||
{
|
||||
builder.Append(next.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.PushBack(next.Value);
|
||||
}
|
||||
int digitCount;
|
||||
next = ConsumeDigits(builder, out digitCount);
|
||||
if (digitCount == 0)
|
||||
{
|
||||
throw reader.CreateException("Invalid numeric literal: exponent without value");
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private char? ConsumeDigits(StringBuilder builder, out int count)
|
||||
{
|
||||
count = 0;
|
||||
while (true)
|
||||
{
|
||||
char? next = reader.Read();
|
||||
if (next == null || next.Value < '0' || next.Value > '9')
|
||||
{
|
||||
return next;
|
||||
}
|
||||
count++;
|
||||
builder.Append(next.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that we're in a valid state to read a value (using the given error prefix if necessary)
|
||||
/// and changes the state to the appropriate one, e.g. ObjectAfterColon to ObjectAfterProperty.
|
||||
/// </summary>
|
||||
private void ValidateAndModifyStateForValue(string errorPrefix)
|
||||
{
|
||||
ValidateState(ValueStates, errorPrefix);
|
||||
switch (state)
|
||||
{
|
||||
case State.StartOfDocument:
|
||||
state = State.ExpectedEndOfDocument;
|
||||
return;
|
||||
case State.ObjectAfterColon:
|
||||
state = State.ObjectAfterProperty;
|
||||
return;
|
||||
case State.ArrayStart:
|
||||
case State.ArrayAfterComma:
|
||||
state = State.ArrayAfterValue;
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException("ValidateAndModifyStateForValue does not handle all value states (and should)");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops the top-most container, and sets the state to the appropriate one for the end of a value
|
||||
/// in the parent container.
|
||||
/// </summary>
|
||||
private void PopContainer()
|
||||
{
|
||||
containerStack.Pop();
|
||||
var parent = containerStack.Peek();
|
||||
switch (parent)
|
||||
{
|
||||
case ContainerType.Object:
|
||||
state = State.ObjectAfterProperty;
|
||||
break;
|
||||
case ContainerType.Array:
|
||||
state = State.ArrayAfterValue;
|
||||
break;
|
||||
case ContainerType.Document:
|
||||
state = State.ExpectedEndOfDocument;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected container type: " + parent);
|
||||
}
|
||||
}
|
||||
|
||||
private enum ContainerType
|
||||
{
|
||||
Document, Object, Array
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible states of the tokenizer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This is a flags enum purely so we can simply and efficiently represent a set of valid states
|
||||
/// for checking.</para>
|
||||
/// <para>
|
||||
/// Each is documented with an example,
|
||||
/// where ^ represents the current position within the text stream. The examples all use string values,
|
||||
/// but could be any value, including nested objects/arrays.
|
||||
/// The complete state of the tokenizer also includes a stack to indicate the contexts (arrays/objects).
|
||||
/// Any additional notional state of "AfterValue" indicates that a value has been completed, at which
|
||||
/// point there's an immediate transition to ExpectedEndOfDocument, ObjectAfterProperty or ArrayAfterValue.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// These states were derived manually by reading RFC 7159 carefully.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
private enum State
|
||||
{
|
||||
/// <summary>
|
||||
/// ^ { "foo": "bar" }
|
||||
/// Before the value in a document. Next states: ObjectStart, ArrayStart, "AfterValue"
|
||||
/// </summary>
|
||||
StartOfDocument = 1 << 0,
|
||||
/// <summary>
|
||||
/// { "foo": "bar" } ^
|
||||
/// After the value in a document. Next states: ReaderExhausted
|
||||
/// </summary>
|
||||
ExpectedEndOfDocument = 1 << 1,
|
||||
/// <summary>
|
||||
/// { "foo": "bar" } ^ (and already read to the end of the reader)
|
||||
/// Terminal state.
|
||||
/// </summary>
|
||||
ReaderExhausted = 1 << 2,
|
||||
/// <summary>
|
||||
/// { ^ "foo": "bar" }
|
||||
/// Before the *first* property in an object.
|
||||
/// Next states:
|
||||
/// "AfterValue" (empty object)
|
||||
/// ObjectBeforeColon (read a name)
|
||||
/// </summary>
|
||||
ObjectStart = 1 << 3,
|
||||
/// <summary>
|
||||
/// { "foo" ^ : "bar", "x": "y" }
|
||||
/// Next state: ObjectAfterColon
|
||||
/// </summary>
|
||||
ObjectBeforeColon = 1 << 4,
|
||||
/// <summary>
|
||||
/// { "foo" : ^ "bar", "x": "y" }
|
||||
/// Before any property other than the first in an object.
|
||||
/// (Equivalently: after any property in an object)
|
||||
/// Next states:
|
||||
/// "AfterValue" (value is simple)
|
||||
/// ObjectStart (value is object)
|
||||
/// ArrayStart (value is array)
|
||||
/// </summary>
|
||||
ObjectAfterColon = 1 << 5,
|
||||
/// <summary>
|
||||
/// { "foo" : "bar" ^ , "x" : "y" }
|
||||
/// At the end of a property, so expecting either a comma or end-of-object
|
||||
/// Next states: ObjectAfterComma or "AfterValue"
|
||||
/// </summary>
|
||||
ObjectAfterProperty = 1 << 6,
|
||||
/// <summary>
|
||||
/// { "foo":"bar", ^ "x":"y" }
|
||||
/// Read the comma after the previous property, so expecting another property.
|
||||
/// This is like ObjectStart, but closing brace isn't valid here
|
||||
/// Next state: ObjectBeforeColon.
|
||||
/// </summary>
|
||||
ObjectAfterComma = 1 << 7,
|
||||
/// <summary>
|
||||
/// [ ^ "foo", "bar" ]
|
||||
/// Before the *first* value in an array.
|
||||
/// Next states:
|
||||
/// "AfterValue" (read a value)
|
||||
/// "AfterValue" (end of array; will pop stack)
|
||||
/// </summary>
|
||||
ArrayStart = 1 << 8,
|
||||
/// <summary>
|
||||
/// [ "foo" ^ , "bar" ]
|
||||
/// After any value in an array, so expecting either a comma or end-of-array
|
||||
/// Next states: ArrayAfterComma or "AfterValue"
|
||||
/// </summary>
|
||||
ArrayAfterValue = 1 << 9,
|
||||
/// <summary>
|
||||
/// [ "foo", ^ "bar" ]
|
||||
/// After a comma in an array, so there *must* be another value (simple or complex).
|
||||
/// Next states: "AfterValue" (simple value), StartObject, StartArray
|
||||
/// </summary>
|
||||
ArrayAfterComma = 1 << 10
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around a text reader allowing small amounts of buffering and location handling.
|
||||
/// </summary>
|
||||
private class PushBackReader
|
||||
{
|
||||
// TODO: Add locations for errors etc.
|
||||
|
||||
private readonly TextReader reader;
|
||||
|
||||
internal PushBackReader(TextReader reader)
|
||||
{
|
||||
// TODO: Wrap the reader in a BufferedReader?
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The buffered next character, if we have one.
|
||||
/// </summary>
|
||||
private char? nextChar;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next character in the stream, or null if we have reached the end.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal char? Read()
|
||||
{
|
||||
if (nextChar != null)
|
||||
{
|
||||
char? tmp = nextChar;
|
||||
nextChar = null;
|
||||
return tmp;
|
||||
}
|
||||
int next = reader.Read();
|
||||
return next == -1 ? null : (char?) next;
|
||||
}
|
||||
|
||||
internal char ReadOrFail(string messageOnFailure)
|
||||
{
|
||||
char? next = Read();
|
||||
if (next == null)
|
||||
{
|
||||
throw CreateException(messageOnFailure);
|
||||
}
|
||||
return next.Value;
|
||||
}
|
||||
|
||||
internal void PushBack(char c)
|
||||
{
|
||||
if (nextChar != null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot push back when already buffering a character");
|
||||
}
|
||||
nextChar = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new exception appropriate for the current state of the reader.
|
||||
/// </summary>
|
||||
internal InvalidJsonException CreateException(string message)
|
||||
{
|
||||
// TODO: Keep track of and use the location.
|
||||
return new InvalidJsonException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>C# runtime library for Protocol Buffers - Google's data interchange format.</Description>
|
||||
<Copyright>Copyright 2015, Google Inc.</Copyright>
|
||||
<AssemblyTitle>Google Protocol Buffers</AssemblyTitle>
|
||||
<VersionPrefix>3.15.6</VersionPrefix>
|
||||
<!-- C# 7.2 is required for Span/BufferWriter/ReadOnlySequence -->
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<Authors>Google Inc.</Authors>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<AssemblyOriginatorKeyFile>./keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PackageTags>Protocol;Buffers;Binary;Serialization;Format;Google;proto;proto3</PackageTags>
|
||||
<PackageReleaseNotes>C# proto3 support</PackageReleaseNotes>
|
||||
<PackageProjectUrl>https://github.com/protocolbuffers/protobuf</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/protocolbuffers/protobuf/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/protocolbuffers/protobuf.git</RepositoryUrl>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<!-- Include PDB in the built .nupkg -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<ReleaseVersion>0.7.1</ReleaseVersion>
|
||||
<AssemblyName>LC.Google.Protobuf</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_FAST_STRING</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net50' ">
|
||||
<DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_FAST_STRING;GOOGLE_PROTOBUF_SIMD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
|
||||
<!-- Needed for the net45 build to work on Unix. See https://github.com/dotnet/designs/pull/33 -->
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,110 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Stream implementation which proxies another stream, only allowing a certain amount
|
||||
/// of data to be read. Note that this is only used to read delimited streams, so it
|
||||
/// doesn't attempt to implement everything.
|
||||
/// </summary>
|
||||
internal sealed class LimitedInputStream : Stream
|
||||
{
|
||||
private readonly Stream proxied;
|
||||
private int bytesLeft;
|
||||
|
||||
internal LimitedInputStream(Stream proxied, int size)
|
||||
{
|
||||
this.proxied = proxied;
|
||||
bytesLeft = size;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (bytesLeft > 0)
|
||||
{
|
||||
int bytesRead = proxied.Read(buffer, offset, Math.Min(bytesLeft, count));
|
||||
bytesLeft -= bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
#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
|
||||
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods on <see cref="IMessage"/> and <see cref="IMessage{T}"/>.
|
||||
/// </summary>
|
||||
public static class MessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges data from the given byte array into an existing message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to merge the data into.</param>
|
||||
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
|
||||
public static void MergeFrom(this IMessage message, byte[] data) =>
|
||||
MergeFrom(message, data, false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Merges data from the given byte array slice into an existing message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to merge the data into.</param>
|
||||
/// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
|
||||
/// <param name="offset">The offset of the slice to merge.</param>
|
||||
/// <param name="length">The length of the slice to merge.</param>
|
||||
public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
|
||||
MergeFrom(message, data, offset, length, false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Merges data from the given byte string into an existing message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to merge the data into.</param>
|
||||
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
|
||||
public static void MergeFrom(this IMessage message, ByteString data) =>
|
||||
MergeFrom(message, data, false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Merges data from the given stream into an existing message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to merge the data into.</param>
|
||||
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
|
||||
public static void MergeFrom(this IMessage message, Stream input) =>
|
||||
MergeFrom(message, input, false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Merges length-delimited data from the given stream into an existing message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stream is expected to contain a length and then the data. Only the amount of data
|
||||
/// specified by the length will be consumed.
|
||||
/// </remarks>
|
||||
/// <param name="message">The message to merge the data into.</param>
|
||||
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
|
||||
public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
|
||||
MergeDelimitedFrom(message, input, false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given message into a byte array in protobuf encoding.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to convert.</param>
|
||||
/// <returns>The message data as a byte array.</returns>
|
||||
public static byte[] ToByteArray(this IMessage message)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
byte[] result = new byte[message.CalculateSize()];
|
||||
CodedOutputStream output = new CodedOutputStream(result);
|
||||
message.WriteTo(output);
|
||||
output.CheckNoSpaceLeft();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given message data to the given stream in protobuf encoding.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write to the stream.</param>
|
||||
/// <param name="output">The stream to write to.</param>
|
||||
public static void WriteTo(this IMessage message, Stream output)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(output, "output");
|
||||
CodedOutputStream codedOutput = new CodedOutputStream(output);
|
||||
message.WriteTo(codedOutput);
|
||||
codedOutput.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the length and then data of the given message to a stream.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write.</param>
|
||||
/// <param name="output">The output stream to write to.</param>
|
||||
public static void WriteDelimitedTo(this IMessage message, Stream output)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(output, "output");
|
||||
CodedOutputStream codedOutput = new CodedOutputStream(output);
|
||||
codedOutput.WriteLength(message.CalculateSize());
|
||||
message.WriteTo(codedOutput);
|
||||
codedOutput.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given message into a byte string in protobuf encoding.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to convert.</param>
|
||||
/// <returns>The message data as a byte string.</returns>
|
||||
public static ByteString ToByteString(this IMessage message)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
return ByteString.AttachBytes(message.ToByteArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given message data to the given buffer writer in protobuf encoding.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write to the stream.</param>
|
||||
/// <param name="output">The stream to write to.</param>
|
||||
[SecuritySafeCritical]
|
||||
public static void WriteTo(this IMessage message, IBufferWriter<byte> output)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, nameof(message));
|
||||
ProtoPreconditions.CheckNotNull(output, nameof(output));
|
||||
|
||||
WriteContext.Initialize(output, out WriteContext ctx);
|
||||
WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
|
||||
ctx.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given message data to the given span in protobuf encoding.
|
||||
/// The size of the destination span needs to fit the serialized size
|
||||
/// of the message exactly, otherwise an exception is thrown.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write to the stream.</param>
|
||||
/// <param name="output">The span to write to. Size must match size of the message exactly.</param>
|
||||
[SecuritySafeCritical]
|
||||
public static void WriteTo(this IMessage message, Span<byte> output)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, nameof(message));
|
||||
|
||||
WriteContext.Initialize(ref output, out WriteContext ctx);
|
||||
WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
|
||||
ctx.CheckNoSpaceLeft();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all required fields in a message have values set. For proto3 messages, this returns true
|
||||
/// </summary>
|
||||
public static bool IsInitialized(this IMessage message)
|
||||
{
|
||||
if (message.Descriptor.File.Syntax == Syntax.Proto3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!message.Descriptor.IsExtensionsInitialized(message))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return message.Descriptor
|
||||
.Fields
|
||||
.InDeclarationOrder()
|
||||
.All(f =>
|
||||
{
|
||||
if (f.IsMap)
|
||||
{
|
||||
var valueField = f.MessageType.Fields[2];
|
||||
if (valueField.FieldType == FieldType.Message)
|
||||
{
|
||||
var map = (IDictionary)f.Accessor.GetValue(message);
|
||||
return map.Values.Cast<IMessage>().All(IsInitialized);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
|
||||
{
|
||||
var enumerable = (IEnumerable)f.Accessor.GetValue(message);
|
||||
return enumerable.Cast<IMessage>().All(IsInitialized);
|
||||
}
|
||||
else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
|
||||
{
|
||||
if (f.Accessor.HasValue(message))
|
||||
{
|
||||
return ((IMessage)f.Accessor.GetValue(message)).IsInitialized();
|
||||
}
|
||||
else
|
||||
{
|
||||
return !f.IsRequired;
|
||||
}
|
||||
}
|
||||
else if (f.IsRequired)
|
||||
{
|
||||
return f.Accessor.HasValue(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Implementations allowing unknown fields to be discarded.
|
||||
internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(data, "data");
|
||||
CodedInputStream input = new CodedInputStream(data);
|
||||
input.DiscardUnknownFields = discardUnknownFields;
|
||||
input.ExtensionRegistry = registry;
|
||||
message.MergeFrom(input);
|
||||
input.CheckReadEndOfStreamTag();
|
||||
}
|
||||
|
||||
internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(data, "data");
|
||||
CodedInputStream input = new CodedInputStream(data, offset, length);
|
||||
input.DiscardUnknownFields = discardUnknownFields;
|
||||
input.ExtensionRegistry = registry;
|
||||
message.MergeFrom(input);
|
||||
input.CheckReadEndOfStreamTag();
|
||||
}
|
||||
|
||||
internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(data, "data");
|
||||
CodedInputStream input = data.CreateCodedInput();
|
||||
input.DiscardUnknownFields = discardUnknownFields;
|
||||
input.ExtensionRegistry = registry;
|
||||
message.MergeFrom(input);
|
||||
input.CheckReadEndOfStreamTag();
|
||||
}
|
||||
|
||||
internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(input, "input");
|
||||
CodedInputStream codedInput = new CodedInputStream(input);
|
||||
codedInput.DiscardUnknownFields = discardUnknownFields;
|
||||
codedInput.ExtensionRegistry = registry;
|
||||
message.MergeFrom(codedInput);
|
||||
codedInput.CheckReadEndOfStreamTag();
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
internal static void MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ParseContext.Initialize(data, out ParseContext ctx);
|
||||
ctx.DiscardUnknownFields = discardUnknownFields;
|
||||
ctx.ExtensionRegistry = registry;
|
||||
ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
|
||||
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
|
||||
}
|
||||
|
||||
internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, "message");
|
||||
ProtoPreconditions.CheckNotNull(input, "input");
|
||||
int size = (int) CodedInputStream.ReadRawVarint32(input);
|
||||
Stream limitedStream = new LimitedInputStream(input, size);
|
||||
MergeFrom(message, limitedStream, discardUnknownFields, registry);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// A general message parser, typically used by reflection-based code as all the methods
|
||||
/// return simple <see cref="IMessage"/>.
|
||||
/// </summary>
|
||||
public class MessageParser
|
||||
{
|
||||
private Func<IMessage> factory;
|
||||
// TODO: When we use a C# 7.1 compiler, make this private protected.
|
||||
internal bool DiscardUnknownFields { get; }
|
||||
|
||||
internal ExtensionRegistry Extensions { get; }
|
||||
|
||||
internal MessageParser(Func<IMessage> factory, bool discardUnknownFields, ExtensionRegistry extensions)
|
||||
{
|
||||
this.factory = factory;
|
||||
DiscardUnknownFields = discardUnknownFields;
|
||||
Extensions = extensions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template instance ready for population.
|
||||
/// </summary>
|
||||
/// <returns>An empty message.</returns>
|
||||
internal IMessage CreateTemplate()
|
||||
{
|
||||
return factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the message. Must not be null.</param>
|
||||
/// <returns>The newly parsed message.</returns>
|
||||
public IMessage ParseFrom(byte[] data)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from a byte array slice.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the message. Must not be null.</param>
|
||||
/// <param name="offset">The offset of the slice to parse.</param>
|
||||
/// <param name="length">The length of the slice to parse.</param>
|
||||
/// <returns>The newly parsed message.</returns>
|
||||
public IMessage ParseFrom(byte[] data, int offset, int length)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeFrom(data, offset, length, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given byte string.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public IMessage ParseFrom(ByteString data)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public IMessage ParseFrom(Stream input)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeFrom(input, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given sequence.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
[SecuritySafeCritical]
|
||||
public IMessage ParseFrom(ReadOnlySequence<byte> data)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a length-delimited message from the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stream is expected to contain a length and then the data. Only the amount of data
|
||||
/// specified by the length will be consumed.
|
||||
/// </remarks>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public IMessage ParseDelimitedFrom(Stream input)
|
||||
{
|
||||
IMessage message = factory();
|
||||
message.MergeDelimitedFrom(input, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given coded input stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public IMessage ParseFrom(CodedInputStream input)
|
||||
{
|
||||
IMessage message = factory();
|
||||
MergeFrom(message, input);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given JSON.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
/// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
|
||||
/// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
||||
public IMessage ParseJson(string json)
|
||||
{
|
||||
IMessage message = factory();
|
||||
JsonParser.Default.Merge(message, json);
|
||||
return message;
|
||||
}
|
||||
|
||||
// TODO: When we're using a C# 7.1 compiler, make this private protected.
|
||||
internal void MergeFrom(IMessage message, CodedInputStream codedInput)
|
||||
{
|
||||
bool originalDiscard = codedInput.DiscardUnknownFields;
|
||||
try
|
||||
{
|
||||
codedInput.DiscardUnknownFields = DiscardUnknownFields;
|
||||
message.MergeFrom(codedInput);
|
||||
}
|
||||
finally
|
||||
{
|
||||
codedInput.DiscardUnknownFields = originalDiscard;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message parser which optionally discards unknown fields when parsing.
|
||||
/// </summary>
|
||||
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
|
||||
/// <returns>A newly configured message parser.</returns>
|
||||
public MessageParser WithDiscardUnknownFields(bool discardUnknownFields) =>
|
||||
new MessageParser(factory, discardUnknownFields, Extensions);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message parser which registers extensions from the specified registry upon creating the message instance
|
||||
/// </summary>
|
||||
/// <param name="registry">The extensions to register</param>
|
||||
/// <returns>A newly configured message parser.</returns>
|
||||
public MessageParser WithExtensionRegistry(ExtensionRegistry registry) =>
|
||||
new MessageParser(factory, DiscardUnknownFields, registry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parser for a specific message type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <p>
|
||||
/// This delegates most behavior to the
|
||||
/// <see cref="IMessage.MergeFrom"/> implementation within the original type, but
|
||||
/// provides convenient overloads to parse from a variety of sources.
|
||||
/// </p>
|
||||
/// <p>
|
||||
/// Most applications will never need to create their own instances of this type;
|
||||
/// instead, use the static <c>Parser</c> property of a generated message type to obtain a
|
||||
/// parser for that type.
|
||||
/// </p>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of message to be parsed.</typeparam>
|
||||
public sealed class MessageParser<T> : MessageParser where T : IMessage<T>
|
||||
{
|
||||
// Implementation note: all the methods here *could* just delegate up to the base class and cast the result.
|
||||
// The current implementation avoids a virtual method call and a cast, which *may* be significant in some cases.
|
||||
// Benchmarking work is required to measure the significance - but it's only a few lines of code in any case.
|
||||
// The API wouldn't change anyway - just the implementation - so this work can be deferred.
|
||||
private readonly Func<T> factory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parser.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The factory method is effectively an optimization over using a generic constraint
|
||||
/// to require a parameterless constructor: delegates are significantly faster to execute.
|
||||
/// </remarks>
|
||||
/// <param name="factory">Function to invoke when a new, empty message is required.</param>
|
||||
public MessageParser(Func<T> factory) : this(factory, false, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal MessageParser(Func<T> factory, bool discardUnknownFields, ExtensionRegistry extensions) : base(() => factory(), discardUnknownFields, extensions)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template instance ready for population.
|
||||
/// </summary>
|
||||
/// <returns>An empty message.</returns>
|
||||
internal new T CreateTemplate()
|
||||
{
|
||||
return factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the message. Must not be null.</param>
|
||||
/// <returns>The newly parsed message.</returns>
|
||||
public new T ParseFrom(byte[] data)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from a byte array slice.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the message. Must not be null.</param>
|
||||
/// <param name="offset">The offset of the slice to parse.</param>
|
||||
/// <param name="length">The length of the slice to parse.</param>
|
||||
/// <returns>The newly parsed message.</returns>
|
||||
public new T ParseFrom(byte[] data, int offset, int length)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeFrom(data, offset, length, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given byte string.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public new T ParseFrom(ByteString data)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public new T ParseFrom(Stream input)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeFrom(input, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given sequence.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
[SecuritySafeCritical]
|
||||
public new T ParseFrom(ReadOnlySequence<byte> data)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeFrom(data, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a length-delimited message from the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stream is expected to contain a length and then the data. Only the amount of data
|
||||
/// specified by the length will be consumed.
|
||||
/// </remarks>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public new T ParseDelimitedFrom(Stream input)
|
||||
{
|
||||
T message = factory();
|
||||
message.MergeDelimitedFrom(input, DiscardUnknownFields, Extensions);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given coded input stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
public new T ParseFrom(CodedInputStream input)
|
||||
{
|
||||
T message = factory();
|
||||
MergeFrom(message, input);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a message from the given JSON.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON to parse.</param>
|
||||
/// <returns>The parsed message.</returns>
|
||||
/// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
|
||||
/// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
||||
public new T ParseJson(string json)
|
||||
{
|
||||
T message = factory();
|
||||
JsonParser.Default.Merge(message, json);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message parser which optionally discards unknown fields when parsing.
|
||||
/// </summary>
|
||||
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
|
||||
/// <returns>A newly configured message parser.</returns>
|
||||
public new MessageParser<T> WithDiscardUnknownFields(bool discardUnknownFields) =>
|
||||
new MessageParser<T>(factory, discardUnknownFields, Extensions);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message parser which registers extensions from the specified registry upon creating the message instance
|
||||
/// </summary>
|
||||
/// <param name="registry">The extensions to register</param>
|
||||
/// <returns>A newly configured message parser.</returns>
|
||||
public new MessageParser<T> WithExtensionRegistry(ExtensionRegistry registry) =>
|
||||
new MessageParser<T>(factory, DiscardUnknownFields, registry);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct used to hold the keys for the fieldByNumber table in DescriptorPool and the keys for the
|
||||
/// extensionByNumber table in ExtensionRegistry.
|
||||
/// </summary>
|
||||
internal struct ObjectIntPair<T> : IEquatable<ObjectIntPair<T>> where T : class
|
||||
{
|
||||
private readonly int number;
|
||||
private readonly T obj;
|
||||
|
||||
internal ObjectIntPair(T obj, int number)
|
||||
{
|
||||
this.number = number;
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
public bool Equals(ObjectIntPair<T> other)
|
||||
{
|
||||
return obj == other.obj
|
||||
&& number == other.number;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is ObjectIntPair<T>)
|
||||
{
|
||||
return Equals((ObjectIntPair<T>)obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return obj.GetHashCode() * ((1 << 16) - 1) + number;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// An opaque struct that represents the current parsing state and is passed along
|
||||
/// as the parsing proceeds.
|
||||
/// All the public methods are intended to be invoked only by the generated code,
|
||||
/// users should never invoke them directly.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public ref struct ParseContext
|
||||
{
|
||||
internal const int DefaultRecursionLimit = 100;
|
||||
internal const int DefaultSizeLimit = Int32.MaxValue;
|
||||
|
||||
internal ReadOnlySpan<byte> buffer;
|
||||
internal ParserInternalState state;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, out ParseContext ctx)
|
||||
{
|
||||
ctx.buffer = buffer;
|
||||
ctx.state = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ParseContext instance from CodedInputStream.
|
||||
/// WARNING: internally this copies the CodedInputStream's state, so after done with the ParseContext,
|
||||
/// the CodedInputStream's state needs to be updated.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(CodedInputStream input, out ParseContext ctx)
|
||||
{
|
||||
ctx.buffer = new ReadOnlySpan<byte>(input.InternalBuffer);
|
||||
// ideally we would use a reference to the original state, but that doesn't seem possible
|
||||
// so we just copy the struct that holds the state. We will need to later store the state back
|
||||
// into CodedInputStream if we want to keep it usable.
|
||||
ctx.state = input.InternalState;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(ReadOnlySequence<byte> input, out ParseContext ctx)
|
||||
{
|
||||
Initialize(input, DefaultRecursionLimit, out ctx);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(ReadOnlySequence<byte> input, int recursionLimit, out ParseContext ctx)
|
||||
{
|
||||
ctx.buffer = default;
|
||||
ctx.state = default;
|
||||
ctx.state.lastTag = 0;
|
||||
ctx.state.recursionDepth = 0;
|
||||
ctx.state.sizeLimit = DefaultSizeLimit;
|
||||
ctx.state.recursionLimit = recursionLimit;
|
||||
ctx.state.currentLimit = int.MaxValue;
|
||||
SegmentedBufferHelper.Initialize(input, out ctx.state.segmentedBufferHelper, out ctx.buffer);
|
||||
ctx.state.bufferPos = 0;
|
||||
ctx.state.bufferSize = ctx.buffer.Length;
|
||||
|
||||
ctx.state.DiscardUnknownFields = false;
|
||||
ctx.state.ExtensionRegistry = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last tag read, or 0 if no tags have been read or we've read beyond
|
||||
/// the end of the input.
|
||||
/// </summary>
|
||||
internal uint LastTag { get { return state.lastTag; } }
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
|
||||
/// </summary>
|
||||
internal bool DiscardUnknownFields {
|
||||
get { return state.DiscardUnknownFields; }
|
||||
set { state.DiscardUnknownFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
|
||||
/// </summary>
|
||||
internal ExtensionRegistry ExtensionRegistry
|
||||
{
|
||||
get { return state.ExtensionRegistry; }
|
||||
set { state.ExtensionRegistry = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a field tag, returning the tag of 0 for "end of input".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this method returns 0, it doesn't necessarily mean the end of all
|
||||
/// the data in this CodedInputReader; it may be the end of the logical input
|
||||
/// for an embedded message, for example.
|
||||
/// </remarks>
|
||||
/// <returns>The next field tag, or 0 for end of input. (0 is never a valid tag.)</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ReadTag()
|
||||
{
|
||||
return ParsingPrimitives.ParseTag(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a double field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double ReadDouble()
|
||||
{
|
||||
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a float field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public float ReadFloat()
|
||||
{
|
||||
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a uint64 field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong ReadUInt64()
|
||||
{
|
||||
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int64 field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long ReadInt64()
|
||||
{
|
||||
return (long)ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an int32 field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadInt32()
|
||||
{
|
||||
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed64 field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong ReadFixed64()
|
||||
{
|
||||
return ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed32 field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ReadFixed32()
|
||||
{
|
||||
return ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bool field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ReadBool()
|
||||
{
|
||||
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state) != 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads a string field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadString()
|
||||
{
|
||||
return ParsingPrimitives.ReadString(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an embedded message field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadMessage(IMessage message)
|
||||
{
|
||||
ParsingPrimitivesMessages.ReadMessage(ref this, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an embedded group field from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadGroup(IMessage message)
|
||||
{
|
||||
ParsingPrimitivesMessages.ReadGroup(ref this, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bytes field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByteString ReadBytes()
|
||||
{
|
||||
return ParsingPrimitives.ReadBytes(ref buffer, ref state);
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads a uint32 field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
return ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an enum field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadEnum()
|
||||
{
|
||||
// Currently just a pass-through, but it's nice to separate it logically from WriteInt32.
|
||||
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sfixed32 field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadSFixed32()
|
||||
{
|
||||
return (int)ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sfixed64 field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long ReadSFixed64()
|
||||
{
|
||||
return (long)ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sint32 field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadSInt32()
|
||||
{
|
||||
return ParsingPrimitives.DecodeZigZag32(ParsingPrimitives.ParseRawVarint32(ref buffer, ref state));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an sint64 field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long ReadSInt64()
|
||||
{
|
||||
return ParsingPrimitives.DecodeZigZag64(ParsingPrimitives.ParseRawVarint64(ref buffer, ref state));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length for length-delimited data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internally just reading a varint, but this method exists
|
||||
/// to make the calling code clearer.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadLength()
|
||||
{
|
||||
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
internal void CopyStateTo(CodedInputStream input)
|
||||
{
|
||||
input.InternalState = state;
|
||||
}
|
||||
|
||||
internal void LoadStateFrom(CodedInputStream input)
|
||||
{
|
||||
state = input.InternalState;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
|
||||
// warning: this is a mutable struct, so it needs to be only passed as a ref!
|
||||
internal struct ParserInternalState
|
||||
{
|
||||
// NOTE: the Span representing the current buffer is kept separate so that this doesn't have to be a ref struct and so it can
|
||||
// be included in CodedInputStream's internal state
|
||||
|
||||
/// <summary>
|
||||
/// The position within the current buffer (i.e. the next byte to read)
|
||||
/// </summary>
|
||||
internal int bufferPos;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the current buffer
|
||||
/// </summary>
|
||||
internal int bufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// If we are currently inside a length-delimited block, this is the number of
|
||||
/// bytes in the buffer that are still available once we leave the delimited block.
|
||||
/// </summary>
|
||||
internal int bufferSizeAfterLimit;
|
||||
|
||||
/// <summary>
|
||||
/// The absolute position of the end of the current length-delimited block (including totalBytesRetired)
|
||||
/// </summary>
|
||||
internal int currentLimit;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of consumed before the start of the current buffer. The
|
||||
/// total bytes read up to the current position can be computed as
|
||||
/// totalBytesRetired + bufferPos.
|
||||
/// </summary>
|
||||
internal int totalBytesRetired;
|
||||
|
||||
internal int recursionDepth; // current recursion depth
|
||||
|
||||
internal SegmentedBufferHelper segmentedBufferHelper;
|
||||
|
||||
/// <summary>
|
||||
/// The last tag we read. 0 indicates we've read to the end of the stream
|
||||
/// (or haven't read anything yet).
|
||||
/// </summary>
|
||||
internal uint lastTag;
|
||||
|
||||
/// <summary>
|
||||
/// The next tag, used to store the value read by PeekTag.
|
||||
/// </summary>
|
||||
internal uint nextTag;
|
||||
internal bool hasNextTag;
|
||||
|
||||
// these fields are configuration, they should be readonly
|
||||
internal int sizeLimit;
|
||||
internal int recursionLimit;
|
||||
|
||||
// If non-null, the top level parse method was started with given coded input stream as an argument
|
||||
// which also means we can potentially fallback to calling MergeFrom(CodedInputStream cis) if needed.
|
||||
internal CodedInputStream CodedInputStream => segmentedBufferHelper.CodedInputStream;
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
|
||||
/// </summary>
|
||||
internal bool DiscardUnknownFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
|
||||
/// </summary>
|
||||
internal ExtensionRegistry ExtensionRegistry { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,815 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Primitives for parsing protobuf wire format.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
internal static class ParsingPrimitives
|
||||
{
|
||||
private const int StackallocThreshold = 256;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length for length-delimited data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internally just reading a varint, but this method exists
|
||||
/// to make the calling code clearer.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ParseLength(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
return (int)ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the next tag.
|
||||
/// If the end of logical stream was reached, an invalid tag of 0 is returned.
|
||||
/// </summary>
|
||||
public static uint ParseTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// The "nextTag" logic is there only as an optimization for reading non-packed repeated / map
|
||||
// fields and is strictly speaking not necessary.
|
||||
// TODO(jtattermusch): look into simplifying the ParseTag logic.
|
||||
if (state.hasNextTag)
|
||||
{
|
||||
state.lastTag = state.nextTag;
|
||||
state.hasNextTag = false;
|
||||
return state.lastTag;
|
||||
}
|
||||
|
||||
// Optimize for the incredibly common case of having at least two bytes left in the buffer,
|
||||
// and those two bytes being enough to get the tag. This will be true for fields up to 4095.
|
||||
if (state.bufferPos + 2 <= state.bufferSize)
|
||||
{
|
||||
int tmp = buffer[state.bufferPos++];
|
||||
if (tmp < 128)
|
||||
{
|
||||
state.lastTag = (uint)tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
int result = tmp & 0x7f;
|
||||
if ((tmp = buffer[state.bufferPos++]) < 128)
|
||||
{
|
||||
result |= tmp << 7;
|
||||
state.lastTag = (uint) result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nope, rewind and go the potentially slow route.
|
||||
state.bufferPos -= 2;
|
||||
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SegmentedBufferHelper.IsAtEnd(ref buffer, ref state))
|
||||
{
|
||||
state.lastTag = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
if (WireFormat.GetTagFieldNumber(state.lastTag) == 0)
|
||||
{
|
||||
// If we actually read a tag with a field of 0, that's not a valid tag.
|
||||
throw InvalidProtocolBufferException.InvalidTag();
|
||||
}
|
||||
return state.lastTag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
|
||||
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
|
||||
/// stream is left in the original position and the method returns <c>false</c>.
|
||||
/// </summary>
|
||||
public static bool MaybeConsumeTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint tag)
|
||||
{
|
||||
if (PeekTag(ref buffer, ref state) == tag)
|
||||
{
|
||||
state.hasNextTag = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks at the next field tag. This is like calling <see cref="ParseTag"/>, but the
|
||||
/// tag is not consumed. (So a subsequent call to <see cref="ParseTag"/> will return the
|
||||
/// same value.)
|
||||
/// </summary>
|
||||
public static uint PeekTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
if (state.hasNextTag)
|
||||
{
|
||||
return state.nextTag;
|
||||
}
|
||||
|
||||
uint savedLast = state.lastTag;
|
||||
state.nextTag = ParseTag(ref buffer, ref state);
|
||||
state.hasNextTag = true;
|
||||
state.lastTag = savedLast; // Undo the side effect of ReadTag
|
||||
return state.nextTag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a raw varint.
|
||||
/// </summary>
|
||||
public static ulong ParseRawVarint64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
if (state.bufferPos + 10 > state.bufferSize)
|
||||
{
|
||||
return ParseRawVarint64SlowPath(ref buffer, ref state);
|
||||
}
|
||||
|
||||
ulong result = buffer[state.bufferPos++];
|
||||
if (result < 128)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
result &= 0x7f;
|
||||
int shift = 7;
|
||||
do
|
||||
{
|
||||
byte b = buffer[state.bufferPos++];
|
||||
result |= (ulong)(b & 0x7F) << shift;
|
||||
if (b < 0x80)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
shift += 7;
|
||||
}
|
||||
while (shift < 64);
|
||||
|
||||
throw InvalidProtocolBufferException.MalformedVarint();
|
||||
}
|
||||
|
||||
private static ulong ParseRawVarint64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int shift = 0;
|
||||
ulong result = 0;
|
||||
do
|
||||
{
|
||||
byte b = ReadRawByte(ref buffer, ref state);
|
||||
result |= (ulong)(b & 0x7F) << shift;
|
||||
if (b < 0x80)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
shift += 7;
|
||||
}
|
||||
while (shift < 64);
|
||||
|
||||
throw InvalidProtocolBufferException.MalformedVarint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a raw Varint. If larger than 32 bits, discard the upper bits.
|
||||
/// This method is optimised for the case where we've got lots of data in the buffer.
|
||||
/// That means we can check the size just once, then just read directly from the buffer
|
||||
/// without constant rechecking of the buffer length.
|
||||
/// </summary>
|
||||
public static uint ParseRawVarint32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
if (state.bufferPos + 5 > state.bufferSize)
|
||||
{
|
||||
return ParseRawVarint32SlowPath(ref buffer, ref state);
|
||||
}
|
||||
|
||||
int tmp = buffer[state.bufferPos++];
|
||||
if (tmp < 128)
|
||||
{
|
||||
return (uint)tmp;
|
||||
}
|
||||
int result = tmp & 0x7f;
|
||||
if ((tmp = buffer[state.bufferPos++]) < 128)
|
||||
{
|
||||
result |= tmp << 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 7;
|
||||
if ((tmp = buffer[state.bufferPos++]) < 128)
|
||||
{
|
||||
result |= tmp << 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 14;
|
||||
if ((tmp = buffer[state.bufferPos++]) < 128)
|
||||
{
|
||||
result |= tmp << 21;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 21;
|
||||
result |= (tmp = buffer[state.bufferPos++]) << 28;
|
||||
if (tmp >= 128)
|
||||
{
|
||||
// Discard upper 32 bits.
|
||||
// Note that this has to use ReadRawByte() as we only ensure we've
|
||||
// got at least 5 bytes at the start of the method. This lets us
|
||||
// use the fast path in more cases, and we rarely hit this section of code.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (ReadRawByte(ref buffer, ref state) < 128)
|
||||
{
|
||||
return (uint) result;
|
||||
}
|
||||
}
|
||||
throw InvalidProtocolBufferException.MalformedVarint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (uint)result;
|
||||
}
|
||||
|
||||
private static uint ParseRawVarint32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int tmp = ReadRawByte(ref buffer, ref state);
|
||||
if (tmp < 128)
|
||||
{
|
||||
return (uint) tmp;
|
||||
}
|
||||
int result = tmp & 0x7f;
|
||||
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
|
||||
{
|
||||
result |= tmp << 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 7;
|
||||
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
|
||||
{
|
||||
result |= tmp << 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 14;
|
||||
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
|
||||
{
|
||||
result |= tmp << 21;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (tmp & 0x7f) << 21;
|
||||
result |= (tmp = ReadRawByte(ref buffer, ref state)) << 28;
|
||||
if (tmp >= 128)
|
||||
{
|
||||
// Discard upper 32 bits.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (ReadRawByte(ref buffer, ref state) < 128)
|
||||
{
|
||||
return (uint) result;
|
||||
}
|
||||
}
|
||||
throw InvalidProtocolBufferException.MalformedVarint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (uint) result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a 32-bit little-endian integer.
|
||||
/// </summary>
|
||||
public static uint ParseRawLittleEndian32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
const int uintLength = sizeof(uint);
|
||||
const int ulongLength = sizeof(ulong);
|
||||
if (state.bufferPos + ulongLength > state.bufferSize)
|
||||
{
|
||||
return ParseRawLittleEndian32SlowPath(ref buffer, ref state);
|
||||
}
|
||||
// ReadUInt32LittleEndian is many times slower than ReadUInt64LittleEndian (at least on some runtimes)
|
||||
// so it's faster better to use ReadUInt64LittleEndian and truncate the result.
|
||||
uint result = (uint) BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, ulongLength));
|
||||
state.bufferPos += uintLength;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint ParseRawLittleEndian32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
uint b1 = ReadRawByte(ref buffer, ref state);
|
||||
uint b2 = ReadRawByte(ref buffer, ref state);
|
||||
uint b3 = ReadRawByte(ref buffer, ref state);
|
||||
uint b4 = ReadRawByte(ref buffer, ref state);
|
||||
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a 64-bit little-endian integer.
|
||||
/// </summary>
|
||||
public static ulong ParseRawLittleEndian64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
const int length = sizeof(ulong);
|
||||
if (state.bufferPos + length > state.bufferSize)
|
||||
{
|
||||
return ParseRawLittleEndian64SlowPath(ref buffer, ref state);
|
||||
}
|
||||
ulong result = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, length));
|
||||
state.bufferPos += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong ParseRawLittleEndian64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
ulong b1 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b2 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b3 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b4 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b5 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b6 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b7 = ReadRawByte(ref buffer, ref state);
|
||||
ulong b8 = ReadRawByte(ref buffer, ref state);
|
||||
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
|
||||
| (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a double value.
|
||||
/// </summary>
|
||||
public static double ParseDouble(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
const int length = sizeof(double);
|
||||
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
|
||||
{
|
||||
return BitConverter.Int64BitsToDouble((long)ParseRawLittleEndian64(ref buffer, ref state));
|
||||
}
|
||||
// ReadUnaligned uses processor architecture for endianness.
|
||||
double result = Unsafe.ReadUnaligned<double>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
|
||||
state.bufferPos += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a float value.
|
||||
/// </summary>
|
||||
public static float ParseFloat(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
const int length = sizeof(float);
|
||||
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
|
||||
{
|
||||
return ParseFloatSlow(ref buffer, ref state);
|
||||
}
|
||||
// ReadUnaligned uses processor architecture for endianness.
|
||||
float result = Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
|
||||
state.bufferPos += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static unsafe float ParseFloatSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
const int length = sizeof(float);
|
||||
byte* stackBuffer = stackalloc byte[length];
|
||||
Span<byte> tempSpan = new Span<byte>(stackBuffer, length);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
tempSpan[i] = ReadRawByte(ref buffer, ref state);
|
||||
}
|
||||
|
||||
// Content is little endian. Reverse if needed to match endianness of architecture.
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
tempSpan.Reverse();
|
||||
}
|
||||
return Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a fixed size of bytes from the input.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">
|
||||
/// the end of the stream or the current limit was reached
|
||||
/// </exception>
|
||||
public static byte[] ReadRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.NegativeSize();
|
||||
}
|
||||
|
||||
if (size <= state.bufferSize - state.bufferPos)
|
||||
{
|
||||
// We have all the bytes we need already.
|
||||
byte[] bytes = new byte[size];
|
||||
buffer.Slice(state.bufferPos, size).CopyTo(bytes);
|
||||
state.bufferPos += size;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return ReadRawBytesSlow(ref buffer, ref state, size);
|
||||
}
|
||||
|
||||
private static byte[] ReadRawBytesSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
|
||||
{
|
||||
ValidateCurrentLimit(ref buffer, ref state, size);
|
||||
|
||||
if ((!state.segmentedBufferHelper.TotalLength.HasValue && size < buffer.Length) ||
|
||||
IsDataAvailableInSource(ref state, size))
|
||||
{
|
||||
// Reading more bytes than are in the buffer, but not an excessive number
|
||||
// of bytes. We can safely allocate the resulting array ahead of time.
|
||||
|
||||
byte[] bytes = new byte[size];
|
||||
ReadRawBytesIntoSpan(ref buffer, ref state, size, bytes);
|
||||
return bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The size is very large. For security reasons, we can't allocate the
|
||||
// entire byte array yet. The size comes directly from the input, so a
|
||||
// maliciously-crafted message could provide a bogus very large size in
|
||||
// order to trick the app into allocating a lot of memory. We avoid this
|
||||
// by allocating and reading only a small chunk at a time, so that the
|
||||
// malicious message must actually *be* extremely large to cause
|
||||
// problems. Meanwhile, we limit the allowed size of a message elsewhere.
|
||||
|
||||
List<byte[]> chunks = new List<byte[]>();
|
||||
|
||||
int pos = state.bufferSize - state.bufferPos;
|
||||
byte[] firstChunk = new byte[pos];
|
||||
buffer.Slice(state.bufferPos, pos).CopyTo(firstChunk);
|
||||
chunks.Add(firstChunk);
|
||||
state.bufferPos = state.bufferSize;
|
||||
|
||||
// Read all the rest of the bytes we need.
|
||||
int sizeLeft = size - pos;
|
||||
while (sizeLeft > 0)
|
||||
{
|
||||
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
|
||||
byte[] chunk = new byte[Math.Min(sizeLeft, state.bufferSize)];
|
||||
|
||||
buffer.Slice(0, chunk.Length)
|
||||
.CopyTo(chunk);
|
||||
state.bufferPos += chunk.Length;
|
||||
sizeLeft -= chunk.Length;
|
||||
chunks.Add(chunk);
|
||||
}
|
||||
|
||||
// OK, got everything. Now concatenate it all into one buffer.
|
||||
byte[] bytes = new byte[size];
|
||||
int newPos = 0;
|
||||
foreach (byte[] chunk in chunks)
|
||||
{
|
||||
Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length);
|
||||
newPos += chunk.Length;
|
||||
}
|
||||
|
||||
// Done.
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and discards <paramref name="size"/> bytes.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">the end of the stream
|
||||
/// or the current limit was reached</exception>
|
||||
public static void SkipRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.NegativeSize();
|
||||
}
|
||||
|
||||
ValidateCurrentLimit(ref buffer, ref state, size);
|
||||
|
||||
if (size <= state.bufferSize - state.bufferPos)
|
||||
{
|
||||
// We have all the bytes we need already.
|
||||
state.bufferPos += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skipping more bytes than are in the buffer. First skip what we have.
|
||||
int pos = state.bufferSize - state.bufferPos;
|
||||
state.bufferPos = state.bufferSize;
|
||||
|
||||
// TODO: If our segmented buffer is backed by a Stream that is seekable, we could skip the bytes more efficiently
|
||||
// by simply updating stream's Position property. This used to be supported in the past, but the support was dropped
|
||||
// because it would make the segmentedBufferHelper more complex. Support can be reintroduced if needed.
|
||||
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
|
||||
|
||||
while (size - pos > state.bufferSize)
|
||||
{
|
||||
pos += state.bufferSize;
|
||||
state.bufferPos = state.bufferSize;
|
||||
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
|
||||
}
|
||||
|
||||
state.bufferPos = size - pos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string ReadString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
return ParsingPrimitives.ReadRawString(ref buffer, ref state, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bytes field value from the input.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ByteString ReadBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
return ByteString.AttachBytes(ParsingPrimitives.ReadRawBytes(ref buffer, ref state, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UTF-8 string from the next "length" bytes.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">
|
||||
/// the end of the stream or the current limit was reached
|
||||
/// </exception>
|
||||
[SecuritySafeCritical]
|
||||
public static string ReadRawString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
|
||||
{
|
||||
// No need to read any data for an empty string.
|
||||
if (length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.NegativeSize();
|
||||
}
|
||||
|
||||
#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
|
||||
if (length <= state.bufferSize - state.bufferPos)
|
||||
{
|
||||
// Fast path: all bytes to decode appear in the same span.
|
||||
ReadOnlySpan<byte> data = buffer.Slice(state.bufferPos, length);
|
||||
|
||||
string value;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* sourceBytes = &MemoryMarshal.GetReference(data))
|
||||
{
|
||||
value = WritingPrimitives.Utf8Encoding.GetString(sourceBytes, length);
|
||||
}
|
||||
}
|
||||
|
||||
state.bufferPos += length;
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ReadStringSlow(ref buffer, ref state, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string assuming that it is spread across multiple spans in a <see cref="ReadOnlySequence{T}"/>.
|
||||
/// </summary>
|
||||
private static string ReadStringSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
|
||||
{
|
||||
ValidateCurrentLimit(ref buffer, ref state, length);
|
||||
|
||||
#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
|
||||
if (IsDataAvailable(ref state, length))
|
||||
{
|
||||
// Read string data into a temporary buffer, either stackalloc'ed or from ArrayPool
|
||||
// Once all data is read then call Encoding.GetString on buffer and return to pool if needed.
|
||||
|
||||
byte[] byteArray = null;
|
||||
Span<byte> byteSpan = length <= StackallocThreshold ?
|
||||
stackalloc byte[length] :
|
||||
(byteArray = ArrayPool<byte>.Shared.Rent(length));
|
||||
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pByteSpan = &MemoryMarshal.GetReference(byteSpan))
|
||||
{
|
||||
// Compiler doesn't like that a potentially stackalloc'd Span<byte> is being used
|
||||
// in a method with a "ref Span<byte> buffer" argument. If the stackalloc'd span was assigned
|
||||
// to the ref argument then bad things would happen. We'll never do that so it is ok.
|
||||
// Make compiler happy by passing a new span created from pointer.
|
||||
var tempSpan = new Span<byte>(pByteSpan, byteSpan.Length);
|
||||
ReadRawBytesIntoSpan(ref buffer, ref state, length, tempSpan);
|
||||
|
||||
return WritingPrimitives.Utf8Encoding.GetString(pByteSpan, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (byteArray != null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(byteArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Slow path: Build a byte array first then copy it.
|
||||
// This will be called when reading from a Stream because we don't know the length of the stream,
|
||||
// or there is not enough data in the sequence. If there is not enough data then ReadRawBytes will
|
||||
// throw an exception.
|
||||
return WritingPrimitives.Utf8Encoding.GetString(ReadRawBytes(ref buffer, ref state, length), 0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the specified size doesn't exceed the current limit. If it does then remaining bytes
|
||||
/// are skipped and an error is thrown.
|
||||
/// </summary>
|
||||
private static void ValidateCurrentLimit(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
|
||||
{
|
||||
if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
|
||||
{
|
||||
// Read to the end of the stream (up to the current limit) anyway.
|
||||
SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
|
||||
// Then fail.
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private static byte ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
if (state.bufferPos == state.bufferSize)
|
||||
{
|
||||
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
|
||||
}
|
||||
return buffer[state.bufferPos++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a varint from the input one byte at a time, so that it does not
|
||||
/// read any bytes after the end of the varint. If you simply wrapped the
|
||||
/// stream in a CodedInputStream and used ReadRawVarint32(Stream)
|
||||
/// then you would probably end up reading past the end of the varint since
|
||||
/// CodedInputStream buffers its input.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static uint ReadRawVarint32(Stream input)
|
||||
{
|
||||
int result = 0;
|
||||
int offset = 0;
|
||||
for (; offset < 32; offset += 7)
|
||||
{
|
||||
int b = input.ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
result |= (b & 0x7f) << offset;
|
||||
if ((b & 0x80) == 0)
|
||||
{
|
||||
return (uint) result;
|
||||
}
|
||||
}
|
||||
// Keep reading up to 64 bits.
|
||||
for (; offset < 64; offset += 7)
|
||||
{
|
||||
int b = input.ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
if ((b & 0x80) == 0)
|
||||
{
|
||||
return (uint) result;
|
||||
}
|
||||
}
|
||||
throw InvalidProtocolBufferException.MalformedVarint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a 32-bit value with ZigZag encoding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ZigZag encodes signed integers into values that can be efficiently
|
||||
/// encoded with varint. (Otherwise, negative values must be
|
||||
/// sign-extended to 32 bits to be varint encoded, thus always taking
|
||||
/// 5 bytes on the wire.)
|
||||
/// </remarks>
|
||||
public static int DecodeZigZag32(uint n)
|
||||
{
|
||||
return (int)(n >> 1) ^ -(int)(n & 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a 64-bit value with ZigZag encoding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ZigZag encodes signed integers into values that can be efficiently
|
||||
/// encoded with varint. (Otherwise, negative values must be
|
||||
/// sign-extended to 64 bits to be varint encoded, thus always taking
|
||||
/// 10 bytes on the wire.)
|
||||
/// </remarks>
|
||||
public static long DecodeZigZag64(ulong n)
|
||||
{
|
||||
return (long)(n >> 1) ^ -(long)(n & 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether there is known data available of the specified size remaining to parse.
|
||||
/// When parsing from a Stream this can return false because we have no knowledge of the amount
|
||||
/// of data remaining in the stream until it is read.
|
||||
/// </summary>
|
||||
public static bool IsDataAvailable(ref ParserInternalState state, int size)
|
||||
{
|
||||
// Data fits in remaining buffer
|
||||
if (size <= state.bufferSize - state.bufferPos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsDataAvailableInSource(ref state, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether there is known data available of the specified size remaining to parse
|
||||
/// in the underlying data source.
|
||||
/// When parsing from a Stream this will return false because we have no knowledge of the amount
|
||||
/// of data remaining in the stream until it is read.
|
||||
/// </summary>
|
||||
private static bool IsDataAvailableInSource(ref ParserInternalState state, int size)
|
||||
{
|
||||
// Data fits in remaining source data.
|
||||
// Note that this will never be true when reading from a stream as the total length is unknown.
|
||||
return size <= state.segmentedBufferHelper.TotalLength - state.totalBytesRetired - state.bufferPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read raw bytes of the specified length into a span. The amount of data available and the current limit should
|
||||
/// be checked before calling this method.
|
||||
/// </summary>
|
||||
private static void ReadRawBytesIntoSpan(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length, Span<byte> byteSpan)
|
||||
{
|
||||
int remainingByteLength = length;
|
||||
while (remainingByteLength > 0)
|
||||
{
|
||||
if (state.bufferSize - state.bufferPos == 0)
|
||||
{
|
||||
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> unreadSpan = buffer.Slice(state.bufferPos, Math.Min(remainingByteLength, state.bufferSize - state.bufferPos));
|
||||
unreadSpan.CopyTo(byteSpan.Slice(length - remainingByteLength));
|
||||
|
||||
remainingByteLength -= unreadSpan.Length;
|
||||
state.bufferPos += unreadSpan.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Reading and skipping messages / groups
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
internal static class ParsingPrimitivesMessages
|
||||
{
|
||||
private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
|
||||
|
||||
public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
if (state.lastTag == 0)
|
||||
{
|
||||
throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
|
||||
}
|
||||
switch (WireFormat.GetTagWireType(state.lastTag))
|
||||
{
|
||||
case WireFormat.WireType.StartGroup:
|
||||
SkipGroup(ref buffer, ref state, state.lastTag);
|
||||
break;
|
||||
case WireFormat.WireType.EndGroup:
|
||||
throw new InvalidProtocolBufferException(
|
||||
"SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
|
||||
case WireFormat.WireType.Fixed32:
|
||||
ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
|
||||
break;
|
||||
case WireFormat.WireType.Fixed64:
|
||||
ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
|
||||
break;
|
||||
case WireFormat.WireType.LengthDelimited:
|
||||
var length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length);
|
||||
break;
|
||||
case WireFormat.WireType.Varint:
|
||||
ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip a group.
|
||||
/// </summary>
|
||||
public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)
|
||||
{
|
||||
// Note: Currently we expect this to be the way that groups are read. We could put the recursion
|
||||
// depth changes into the ReadTag method instead, potentially...
|
||||
state.recursionDepth++;
|
||||
if (state.recursionDepth >= state.recursionLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.RecursionLimitExceeded();
|
||||
}
|
||||
uint tag;
|
||||
while (true)
|
||||
{
|
||||
tag = ParsingPrimitives.ParseTag(ref buffer, ref state);
|
||||
if (tag == 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
// Can't call SkipLastField for this case- that would throw.
|
||||
if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// This recursion will allow us to handle nested groups.
|
||||
SkipLastField(ref buffer, ref state);
|
||||
}
|
||||
int startField = WireFormat.GetTagFieldNumber(startGroupTag);
|
||||
int endField = WireFormat.GetTagFieldNumber(tag);
|
||||
if (startField != endField)
|
||||
{
|
||||
throw new InvalidProtocolBufferException(
|
||||
$"Mismatched end-group tag. Started with field {startField}; ended with field {endField}");
|
||||
}
|
||||
state.recursionDepth--;
|
||||
}
|
||||
|
||||
public static void ReadMessage(ref ParseContext ctx, IMessage message)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
|
||||
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.RecursionLimitExceeded();
|
||||
}
|
||||
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
|
||||
++ctx.state.recursionDepth;
|
||||
|
||||
ReadRawMessage(ref ctx, message);
|
||||
|
||||
CheckReadEndOfStreamTag(ref ctx.state);
|
||||
// Check that we've read exactly as much data as expected.
|
||||
if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
--ctx.state.recursionDepth;
|
||||
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
|
||||
}
|
||||
|
||||
public static KeyValuePair<TKey, TValue> ReadMapEntry<TKey, TValue>(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
|
||||
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.RecursionLimitExceeded();
|
||||
}
|
||||
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
|
||||
++ctx.state.recursionDepth;
|
||||
|
||||
TKey key = codec.KeyCodec.DefaultValue;
|
||||
TValue value = codec.ValueCodec.DefaultValue;
|
||||
|
||||
uint tag;
|
||||
while ((tag = ctx.ReadTag()) != 0)
|
||||
{
|
||||
if (tag == codec.KeyCodec.Tag)
|
||||
{
|
||||
key = codec.KeyCodec.Read(ref ctx);
|
||||
}
|
||||
else if (tag == codec.ValueCodec.Tag)
|
||||
{
|
||||
value = codec.ValueCodec.Read(ref ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipLastField(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
}
|
||||
|
||||
// Corner case: a map entry with a key but no value, where the value type is a message.
|
||||
// Read it as if we'd seen input with no data (i.e. create a "default" message).
|
||||
if (value == null)
|
||||
{
|
||||
if (ctx.state.CodedInputStream != null)
|
||||
{
|
||||
// the decoded message might not support parsing from ParseContext, so
|
||||
// we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing.
|
||||
value = codec.ValueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx);
|
||||
value = codec.ValueCodec.Read(ref zeroLengthCtx);
|
||||
}
|
||||
}
|
||||
|
||||
CheckReadEndOfStreamTag(ref ctx.state);
|
||||
// Check that we've read exactly as much data as expected.
|
||||
if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
--ctx.state.recursionDepth;
|
||||
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
|
||||
|
||||
return new KeyValuePair<TKey, TValue>(key, value);
|
||||
}
|
||||
|
||||
public static void ReadGroup(ref ParseContext ctx, IMessage message)
|
||||
{
|
||||
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.RecursionLimitExceeded();
|
||||
}
|
||||
++ctx.state.recursionDepth;
|
||||
|
||||
uint tag = ctx.state.lastTag;
|
||||
int fieldNumber = WireFormat.GetTagFieldNumber(tag);
|
||||
ReadRawMessage(ref ctx, message);
|
||||
CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
|
||||
|
||||
--ctx.state.recursionDepth;
|
||||
}
|
||||
|
||||
public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)
|
||||
{
|
||||
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.RecursionLimitExceeded();
|
||||
}
|
||||
++ctx.state.recursionDepth;
|
||||
|
||||
set.MergeGroupFrom(ref ctx);
|
||||
CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
|
||||
|
||||
--ctx.state.recursionDepth;
|
||||
}
|
||||
|
||||
public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
|
||||
{
|
||||
if (message is IBufferMessage bufferMessage)
|
||||
{
|
||||
bufferMessage.InternalMergeFrom(ref ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we reached here, it means we've ran into a nested message with older generated code
|
||||
// which doesn't provide the InternalMergeFrom method that takes a ParseContext.
|
||||
// With a slight performance overhead, we can still parse this message just fine,
|
||||
// but we need to find the original CodedInputStream instance that initiated this
|
||||
// parsing process and make sure its internal state is up to date.
|
||||
// Note that this performance overhead is not very high (basically copying contents of a struct)
|
||||
// and it will only be incurred in case the application mixes older and newer generated code.
|
||||
// Regenerating the code from .proto files will remove this overhead because it will
|
||||
// generate the InternalMergeFrom method we need.
|
||||
|
||||
if (ctx.state.CodedInputStream == null)
|
||||
{
|
||||
// This can only happen when the parsing started without providing a CodedInputStream instance
|
||||
// (e.g. ParseContext was created directly from a ReadOnlySequence).
|
||||
// That also means that one of the new parsing APIs was used at the top level
|
||||
// and in such case it is reasonable to require that all the nested message provide
|
||||
// up-to-date generated code with ParseContext support (and fail otherwise).
|
||||
throw new InvalidProtocolBufferException($"Message {message.GetType().Name} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code.");
|
||||
}
|
||||
|
||||
ctx.CopyStateTo(ctx.state.CodedInputStream);
|
||||
try
|
||||
{
|
||||
// fallback parse using the CodedInputStream that started current parsing tree
|
||||
message.MergeFrom(ctx.state.CodedInputStream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.LoadStateFrom(ctx.state.CodedInputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the last call to ReadTag() returned tag 0 - in other words,
|
||||
/// we've reached the end of the stream when we expected to.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidProtocolBufferException">The
|
||||
/// tag read was not the one specified</exception>
|
||||
public static void CheckReadEndOfStreamTag(ref ParserInternalState state)
|
||||
{
|
||||
if (state.lastTag != 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.MoreDataAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag)
|
||||
{
|
||||
if (state.lastTag != expectedTag) {
|
||||
throw InvalidProtocolBufferException.InvalidEndTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Fast parsing primitives for wrapper types
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
internal static class ParsingPrimitivesWrappers
|
||||
{
|
||||
internal static float? ReadFloatWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// length:1 + tag:1 + value:4 = 6 bytes
|
||||
if (state.bufferPos + 6 <= state.bufferSize)
|
||||
{
|
||||
// The entire wrapper message is already contained in `buffer`.
|
||||
int length = buffer[state.bufferPos];
|
||||
if (length == 0)
|
||||
{
|
||||
state.bufferPos++;
|
||||
return 0F;
|
||||
}
|
||||
// tag:1 + value:4 = length of 5 bytes
|
||||
// field=1, type=32-bit = tag of 13
|
||||
if (length != 5 || buffer[state.bufferPos + 1] != 13)
|
||||
{
|
||||
return ReadFloatWrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
state.bufferPos += 2;
|
||||
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadFloatWrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
internal static float? ReadFloatWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
if (length == 0)
|
||||
{
|
||||
return 0F;
|
||||
}
|
||||
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
|
||||
float result = 0F;
|
||||
do
|
||||
{
|
||||
// field=1, type=32-bit = tag of 13
|
||||
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 13)
|
||||
{
|
||||
result = ParsingPrimitives.ParseFloat(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static double? ReadDoubleWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// length:1 + tag:1 + value:8 = 10 bytes
|
||||
if (state.bufferPos + 10 <= state.bufferSize)
|
||||
{
|
||||
// The entire wrapper message is already contained in `buffer`.
|
||||
int length = buffer[state.bufferPos];
|
||||
if (length == 0)
|
||||
{
|
||||
state.bufferPos++;
|
||||
return 0D;
|
||||
}
|
||||
// tag:1 + value:8 = length of 9 bytes
|
||||
// field=1, type=64-bit = tag of 9
|
||||
if (length != 9 || buffer[state.bufferPos + 1] != 9)
|
||||
{
|
||||
return ReadDoubleWrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
state.bufferPos += 2;
|
||||
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadDoubleWrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
internal static double? ReadDoubleWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
if (length == 0)
|
||||
{
|
||||
return 0D;
|
||||
}
|
||||
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
|
||||
double result = 0D;
|
||||
do
|
||||
{
|
||||
// field=1, type=64-bit = tag of 9
|
||||
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 9)
|
||||
{
|
||||
result = ParsingPrimitives.ParseDouble(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static bool? ReadBoolWrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
return ReadUInt64Wrapper(ref buffer, ref state) != 0;
|
||||
}
|
||||
|
||||
internal static uint? ReadUInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// field=1, type=varint = tag of 8
|
||||
const int expectedTag = 8;
|
||||
// length:1 + tag:1 + value:10(varint64-max) = 12 bytes
|
||||
// Value can be 64 bits for negative integers
|
||||
if (state.bufferPos + 12 <= state.bufferSize)
|
||||
{
|
||||
// The entire wrapper message is already contained in `buffer`.
|
||||
int pos0 = state.bufferPos;
|
||||
int length = buffer[state.bufferPos++];
|
||||
if (length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Length will always fit in a single byte.
|
||||
if (length >= 128)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt32WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
int finalBufferPos = state.bufferPos + length;
|
||||
if (buffer[state.bufferPos++] != expectedTag)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt32WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
var result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
// Verify this message only contained a single field.
|
||||
if (state.bufferPos != finalBufferPos)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt32WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadUInt32WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
internal static uint? ReadUInt32WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
if (length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
|
||||
uint result = 0;
|
||||
do
|
||||
{
|
||||
// field=1, type=varint = tag of 8
|
||||
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 8)
|
||||
{
|
||||
result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static int? ReadInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
return (int?)ReadUInt32Wrapper(ref buffer, ref state);
|
||||
}
|
||||
|
||||
internal static ulong? ReadUInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// field=1, type=varint = tag of 8
|
||||
const int expectedTag = 8;
|
||||
// length:1 + tag:1 + value:10(varint64-max) = 12 bytes
|
||||
if (state.bufferPos + 12 <= state.bufferSize)
|
||||
{
|
||||
// The entire wrapper message is already contained in `buffer`.
|
||||
int pos0 = state.bufferPos;
|
||||
int length = buffer[state.bufferPos++];
|
||||
if (length == 0)
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
// Length will always fit in a single byte.
|
||||
if (length >= 128)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt64WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
int finalBufferPos = state.bufferPos + length;
|
||||
if (buffer[state.bufferPos++] != expectedTag)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt64WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
var result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
|
||||
// Verify this message only contained a single field.
|
||||
if (state.bufferPos != finalBufferPos)
|
||||
{
|
||||
state.bufferPos = pos0;
|
||||
return ReadUInt64WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadUInt64WrapperSlow(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
|
||||
internal static ulong? ReadUInt64WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
// field=1, type=varint = tag of 8
|
||||
const int expectedTag = 8;
|
||||
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
|
||||
if (length == 0)
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
|
||||
ulong result = 0L;
|
||||
do
|
||||
{
|
||||
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == expectedTag)
|
||||
{
|
||||
result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
|
||||
}
|
||||
}
|
||||
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static long? ReadInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
return (long?)ReadUInt64Wrapper(ref buffer, ref state);
|
||||
}
|
||||
|
||||
internal static float? ReadFloatWrapperLittleEndian(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static float? ReadFloatWrapperSlow(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadFloatWrapperSlow(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static double? ReadDoubleWrapperLittleEndian(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static double? ReadDoubleWrapperSlow(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadDoubleWrapperSlow(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static bool? ReadBoolWrapper(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadBoolWrapper(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static uint? ReadUInt32Wrapper(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadUInt32Wrapper(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static int? ReadInt32Wrapper(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadInt32Wrapper(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static ulong? ReadUInt64Wrapper(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadUInt64Wrapper(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static ulong? ReadUInt64WrapperSlow(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadUInt64WrapperSlow(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
|
||||
internal static long? ReadInt64Wrapper(ref ParseContext ctx)
|
||||
{
|
||||
return ParsingPrimitivesWrappers.ReadInt64Wrapper(ref ctx.buffer, ref ctx.state);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
#if !NCRUNCH
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
#endif
|
||||
|
||||
[assembly: InternalsVisibleTo("Google.Protobuf.Test, PublicKey=" +
|
||||
"002400000480000094000000060200000024000052534131000400000100010025800fbcfc63a1" +
|
||||
"7c66b303aae80b03a6beaa176bb6bef883be436f2a1579edd80ce23edf151a1f4ced97af83abcd" +
|
||||
"981207041fd5b2da3b498346fcfcd94910d52f25537c4a43ce3fbe17dc7d43e6cbdb4d8f1242dc" +
|
||||
"b6bd9b5906be74da8daa7d7280f97130f318a16c07baf118839b156299a48522f9fae2371c9665" +
|
||||
"c5ae9cb6")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Google.Protobuf.Benchmarks, PublicKey=" +
|
||||
"002400000480000094000000060200000024000052534131000400000100010025800fbcfc63a1" +
|
||||
"7c66b303aae80b03a6beaa176bb6bef883be436f2a1579edd80ce23edf151a1f4ced97af83abcd" +
|
||||
"981207041fd5b2da3b498346fcfcd94910d52f25537c4a43ce3fbe17dc7d43e6cbdb4d8f1242dc" +
|
||||
"b6bd9b5906be74da8daa7d7280f97130f318a16c07baf118839b156299a48522f9fae2371c9665" +
|
||||
"c5ae9cb6")]
|
|
@ -0,0 +1,79 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for throwing exceptions when preconditions are not met.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is used internally and by generated code; it is not particularly
|
||||
/// expected to be used from application code, although nothing prevents it
|
||||
/// from being used that way.
|
||||
/// </remarks>
|
||||
public static class ProtoPreconditions
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an ArgumentNullException if the given value is null, otherwise
|
||||
/// return the value to the caller.
|
||||
/// </summary>
|
||||
public static T CheckNotNull<T>(T value, string name) where T : class
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an ArgumentNullException if the given value is null, otherwise
|
||||
/// return the value to the caller.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is equivalent to <see cref="CheckNotNull{T}(T, string)"/> but without the type parameter
|
||||
/// constraint. In most cases, the constraint is useful to prevent you from calling CheckNotNull
|
||||
/// with a value type - but it gets in the way if either you want to use it with a nullable
|
||||
/// value type, or you want to use it with an unconstrained type parameter.
|
||||
/// </remarks>
|
||||
internal static T CheckNotNullUnconstrained<T>(T value, string name)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for a set of custom options specified within a message, field etc.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type is publicly immutable, but internally mutable. It is only populated
|
||||
/// by the descriptor parsing code - by the time any user code is able to see an instance,
|
||||
/// it will be fully initialized.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If an option is requested using the incorrect method, an answer may still be returned: all
|
||||
/// of the numeric types are represented internally using 64-bit integers, for example. It is up to
|
||||
/// the caller to ensure that they make the appropriate method call for the option they're interested in.
|
||||
/// Note that enum options are simply stored as integers, so the value should be fetched using
|
||||
/// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Repeated options are currently not supported. Asking for a single value of an option
|
||||
/// which was actually repeated will return the last value, except for message types where
|
||||
/// all the set values are merged together.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class CustomOptions
|
||||
{
|
||||
private static readonly object[] EmptyParameters = new object[0];
|
||||
private readonly IDictionary<int, IExtensionValue> values;
|
||||
|
||||
internal CustomOptions(IDictionary<int, IExtensionValue> values)
|
||||
{
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Boolean value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 32-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 64-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field,
|
||||
/// assuming a fixed-length representation.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 32-bit integer value for the specified option field,
|
||||
/// assuming a zigzag encoding.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signed 64-bit integer value for the specified option field,
|
||||
/// assuming a zigzag encoding.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 32-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an unsigned 64-bit integer value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a 32-bit floating point value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a 64-bit floating point value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a string value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bytes value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a message value for the specified option field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to fetch the value for.</param>
|
||||
/// <param name="value">The output variable to populate.</param>
|
||||
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
|
||||
public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
IExtensionValue extensionValue;
|
||||
if (values.TryGetValue(field, out extensionValue))
|
||||
{
|
||||
if (extensionValue is ExtensionValue<T>)
|
||||
{
|
||||
ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
|
||||
ByteString bytes = single.GetValue().ToByteString();
|
||||
value = new T();
|
||||
value.MergeFrom(bytes);
|
||||
return true;
|
||||
}
|
||||
else if (extensionValue is RepeatedExtensionValue<T>)
|
||||
{
|
||||
RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
|
||||
value = repeated.GetValue()
|
||||
.Select(v => v.ToByteString())
|
||||
.Aggregate(new T(), (t, b) =>
|
||||
{
|
||||
t.MergeFrom(b);
|
||||
return t;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetPrimitiveValue<T>(int field, out T value)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
IExtensionValue extensionValue;
|
||||
if (values.TryGetValue(field, out extensionValue))
|
||||
{
|
||||
if (extensionValue is ExtensionValue<T>)
|
||||
{
|
||||
ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
|
||||
value = single.GetValue();
|
||||
return true;
|
||||
}
|
||||
else if (extensionValue is RepeatedExtensionValue<T>)
|
||||
{
|
||||
RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
|
||||
if (repeated.GetValue().Count != 0)
|
||||
{
|
||||
RepeatedField<T> repeatedField = repeated.GetValue();
|
||||
value = repeatedField[repeatedField.Count - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue<Enum>
|
||||
{
|
||||
var type = extensionValue.GetType();
|
||||
if (type.GetGenericTypeDefinition() == typeof(ExtensionValue<>))
|
||||
{
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
var typeArgs = typeInfo.GenericTypeArguments;
|
||||
if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
|
||||
{
|
||||
value = (T)typeInfo.GetDeclaredMethod(nameof(ExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (type.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>))
|
||||
{
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
var typeArgs = typeInfo.GenericTypeArguments;
|
||||
if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
|
||||
{
|
||||
var values = (IList)typeInfo.GetDeclaredMethod(nameof(RepeatedExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
|
||||
if (values.Count != 0)
|
||||
{
|
||||
value = (T)values[values.Count - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,92 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for nearly all descriptors, providing common functionality.
|
||||
/// </summary>
|
||||
public abstract class DescriptorBase : IDescriptor
|
||||
{
|
||||
internal DescriptorBase(FileDescriptor file, string fullName, int index)
|
||||
{
|
||||
File = file;
|
||||
FullName = fullName;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The index of this descriptor within its parent descriptor.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This returns the index of this descriptor within its parent, for
|
||||
/// this descriptor's type. (There can be duplicate values for different
|
||||
/// types, e.g. one enum type with index 0 and one message type with index 0.)
|
||||
/// </remarks>
|
||||
public int Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the entity (field, message etc) being described.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The fully qualified name of the descriptor's target.
|
||||
/// </summary>
|
||||
public string FullName { get; }
|
||||
|
||||
/// <value>
|
||||
/// The file this descriptor was declared in.
|
||||
/// </value>
|
||||
public FileDescriptor File { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The declaration information about the descriptor, or null if no declaration information
|
||||
/// is available for this descriptor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This information is typically only available for dynamically loaded descriptors,
|
||||
/// for example within a protoc plugin where the full descriptors, including source info,
|
||||
/// are passed to the code by protoc.
|
||||
/// </remarks>
|
||||
public DescriptorDeclaration Declaration => File.GetDeclaration(this);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the list of nested descriptors corresponding to the given field number, if any.
|
||||
/// If the field is unknown or not a nested descriptor list, return null to terminate the search.
|
||||
/// The default implementation returns null.
|
||||
/// </summary>
|
||||
internal virtual IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) => null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2018 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using static LC.Google.Protobuf.Reflection.SourceCodeInfo.Types;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides additional information about the declaration of a descriptor,
|
||||
/// such as source location and comments.
|
||||
/// </summary>
|
||||
public sealed class DescriptorDeclaration
|
||||
{
|
||||
/// <summary>
|
||||
/// The descriptor this declaration relates to.
|
||||
/// </summary>
|
||||
public IDescriptor Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The start line of the declaration within the source file. This value is 1-based.
|
||||
/// </summary>
|
||||
public int StartLine { get; }
|
||||
/// <summary>
|
||||
/// The start column of the declaration within the source file. This value is 1-based.
|
||||
/// </summary>
|
||||
public int StartColumn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// // The end line of the declaration within the source file. This value is 1-based.
|
||||
/// </summary>
|
||||
public int EndLine { get; }
|
||||
/// <summary>
|
||||
/// The end column of the declaration within the source file. This value is 1-based, and
|
||||
/// exclusive. (The final character of the declaration is on the column before this value.)
|
||||
/// </summary>
|
||||
public int EndColumn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Comments appearing before the declaration. Never null, but may be empty. Multi-line comments
|
||||
/// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
|
||||
/// are removed from each line.
|
||||
/// </summary>
|
||||
public string LeadingComments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Comments appearing after the declaration. Never null, but may be empty. Multi-line comments
|
||||
/// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
|
||||
/// are removed from each line.
|
||||
/// </summary>
|
||||
public string TrailingComments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Comments appearing before the declaration, but separated from it by blank
|
||||
/// lines. Each string represents a newline-separated paragraph of comments.
|
||||
/// Leading whitespace and the comment marker ("//") are removed from each line.
|
||||
/// The list is never null, but may be empty. Likewise each element is never null, but may be empty.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> LeadingDetachedComments { get; }
|
||||
|
||||
private DescriptorDeclaration(IDescriptor descriptor, Location location)
|
||||
{
|
||||
// TODO: Validation
|
||||
Descriptor = descriptor;
|
||||
bool hasEndLine = location.Span.Count == 4;
|
||||
// Lines and columns are 0-based in the proto.
|
||||
StartLine = location.Span[0] + 1;
|
||||
StartColumn = location.Span[1] + 1;
|
||||
EndLine = hasEndLine ? location.Span[2] + 1 : StartLine;
|
||||
EndColumn = location.Span[hasEndLine ? 3 : 2] + 1;
|
||||
LeadingComments = location.LeadingComments;
|
||||
TrailingComments = location.TrailingComments;
|
||||
LeadingDetachedComments = new ReadOnlyCollection<string>(location.LeadingDetachedComments.ToList());
|
||||
}
|
||||
|
||||
internal static DescriptorDeclaration FromProto(IDescriptor descriptor, Location location) =>
|
||||
new DescriptorDeclaration(descriptor, location);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,334 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains lookup tables containing all the descriptors defined in a particular file.
|
||||
/// </summary>
|
||||
internal sealed class DescriptorPool
|
||||
{
|
||||
private readonly IDictionary<string, IDescriptor> descriptorsByName =
|
||||
new Dictionary<string, IDescriptor>();
|
||||
|
||||
private readonly IDictionary<ObjectIntPair<IDescriptor>, FieldDescriptor> fieldsByNumber =
|
||||
new Dictionary<ObjectIntPair<IDescriptor>, FieldDescriptor>();
|
||||
|
||||
private readonly IDictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor> enumValuesByNumber =
|
||||
new Dictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor>();
|
||||
|
||||
private readonly HashSet<FileDescriptor> dependencies;
|
||||
|
||||
internal DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles)
|
||||
{
|
||||
dependencies = new HashSet<FileDescriptor>();
|
||||
foreach (var dependencyFile in dependencyFiles)
|
||||
{
|
||||
dependencies.Add(dependencyFile);
|
||||
ImportPublicDependencies(dependencyFile);
|
||||
}
|
||||
|
||||
foreach (FileDescriptor dependency in dependencyFiles)
|
||||
{
|
||||
AddPackage(dependency.Package, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportPublicDependencies(FileDescriptor file)
|
||||
{
|
||||
foreach (FileDescriptor dependency in file.PublicDependencies)
|
||||
{
|
||||
if (dependencies.Add(dependency))
|
||||
{
|
||||
ImportPublicDependencies(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a symbol of the given name within the pool.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of symbol to look for</typeparam>
|
||||
/// <param name="fullName">Fully-qualified name to look up</param>
|
||||
/// <returns>The symbol with the given name and type,
|
||||
/// or null if the symbol doesn't exist or has the wrong type</returns>
|
||||
internal T FindSymbol<T>(string fullName) where T : class
|
||||
{
|
||||
IDescriptor result;
|
||||
descriptorsByName.TryGetValue(fullName, out result);
|
||||
T descriptor = result as T;
|
||||
if (descriptor != null)
|
||||
{
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
// dependencies contains direct dependencies and any *public* dependencies
|
||||
// of those dependencies (transitively)... so we don't need to recurse here.
|
||||
foreach (FileDescriptor dependency in dependencies)
|
||||
{
|
||||
dependency.DescriptorPool.descriptorsByName.TryGetValue(fullName, out result);
|
||||
descriptor = result as T;
|
||||
if (descriptor != null)
|
||||
{
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a package to the symbol tables. If a package by the same name
|
||||
/// already exists, that is fine, but if some other kind of symbol
|
||||
/// exists under the same name, an exception is thrown. If the package
|
||||
/// has multiple components, this also adds the parent package(s).
|
||||
/// </summary>
|
||||
internal void AddPackage(string fullName, FileDescriptor file)
|
||||
{
|
||||
int dotpos = fullName.LastIndexOf('.');
|
||||
String name;
|
||||
if (dotpos != -1)
|
||||
{
|
||||
AddPackage(fullName.Substring(0, dotpos), file);
|
||||
name = fullName.Substring(dotpos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = fullName;
|
||||
}
|
||||
|
||||
IDescriptor old;
|
||||
if (descriptorsByName.TryGetValue(fullName, out old))
|
||||
{
|
||||
if (!(old is PackageDescriptor))
|
||||
{
|
||||
throw new DescriptorValidationException(file,
|
||||
"\"" + name +
|
||||
"\" is already defined (as something other than a " +
|
||||
"package) in file \"" + old.File.Name + "\".");
|
||||
}
|
||||
}
|
||||
descriptorsByName[fullName] = new PackageDescriptor(name, fullName, file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a symbol to the symbol table.
|
||||
/// </summary>
|
||||
/// <exception cref="DescriptorValidationException">The symbol already existed
|
||||
/// in the symbol table.</exception>
|
||||
internal void AddSymbol(IDescriptor descriptor)
|
||||
{
|
||||
ValidateSymbolName(descriptor);
|
||||
String fullName = descriptor.FullName;
|
||||
|
||||
IDescriptor old;
|
||||
if (descriptorsByName.TryGetValue(fullName, out old))
|
||||
{
|
||||
int dotPos = fullName.LastIndexOf('.');
|
||||
string message;
|
||||
if (descriptor.File == old.File)
|
||||
{
|
||||
if (dotPos == -1)
|
||||
{
|
||||
message = "\"" + fullName + "\" is already defined.";
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "\"" + fullName.Substring(dotPos + 1) + "\" is already defined in \"" +
|
||||
fullName.Substring(0, dotPos) + "\".";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "\"" + fullName + "\" is already defined in file \"" + old.File.Name + "\".";
|
||||
}
|
||||
throw new DescriptorValidationException(descriptor, message);
|
||||
}
|
||||
descriptorsByName[fullName] = descriptor;
|
||||
}
|
||||
|
||||
private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$",
|
||||
FrameworkPortability.CompiledRegexWhereAvailable);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the descriptor's name is valid (i.e. it contains
|
||||
/// only letters, digits and underscores, and does not start with a digit).
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
private static void ValidateSymbolName(IDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.Name == "")
|
||||
{
|
||||
throw new DescriptorValidationException(descriptor, "Missing name.");
|
||||
}
|
||||
if (!ValidationRegex.IsMatch(descriptor.Name))
|
||||
{
|
||||
throw new DescriptorValidationException(descriptor,
|
||||
"\"" + descriptor.Name + "\" is not a valid identifier.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the field with the given number in the given descriptor,
|
||||
/// or null if it can't be found.
|
||||
/// </summary>
|
||||
internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number)
|
||||
{
|
||||
FieldDescriptor ret;
|
||||
fieldsByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(messageDescriptor, number), out ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)
|
||||
{
|
||||
EnumValueDescriptor ret;
|
||||
enumValuesByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(enumDescriptor, number), out ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a field to the fieldsByNumber table.
|
||||
/// </summary>
|
||||
/// <exception cref="DescriptorValidationException">A field with the same
|
||||
/// containing type and number already exists.</exception>
|
||||
internal void AddFieldByNumber(FieldDescriptor field)
|
||||
{
|
||||
// for extensions, we use the extended type, otherwise we use the containing type
|
||||
ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(field.Proto.HasExtendee ? field.ExtendeeType : field.ContainingType, field.FieldNumber);
|
||||
FieldDescriptor old;
|
||||
if (fieldsByNumber.TryGetValue(key, out old))
|
||||
{
|
||||
throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
|
||||
"has already been used in \"" +
|
||||
field.ContainingType.FullName +
|
||||
"\" by field \"" + old.Name + "\".");
|
||||
}
|
||||
fieldsByNumber[key] = field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an enum value to the enumValuesByNumber table. If an enum value
|
||||
/// with the same type and number already exists, this method does nothing.
|
||||
/// (This is allowed; the first value defined with the number takes precedence.)
|
||||
/// </summary>
|
||||
internal void AddEnumValueByNumber(EnumValueDescriptor enumValue)
|
||||
{
|
||||
ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(enumValue.EnumDescriptor, enumValue.Number);
|
||||
if (!enumValuesByNumber.ContainsKey(key))
|
||||
{
|
||||
enumValuesByNumber[key] = enumValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a descriptor by name, relative to some other descriptor.
|
||||
/// The name may be fully-qualified (with a leading '.'), partially-qualified,
|
||||
/// or unqualified. C++-like name lookup semantics are used to search for the
|
||||
/// matching descriptor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This isn't heavily optimized, but it's only used during cross linking anyway.
|
||||
/// If it starts being used more widely, we should look at performance more carefully.
|
||||
/// </remarks>
|
||||
internal IDescriptor LookupSymbol(string name, IDescriptor relativeTo)
|
||||
{
|
||||
IDescriptor result;
|
||||
if (name.StartsWith("."))
|
||||
{
|
||||
// Fully-qualified name.
|
||||
result = FindSymbol<IDescriptor>(name.Substring(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If "name" is a compound identifier, we want to search for the
|
||||
// first component of it, then search within it for the rest.
|
||||
int firstPartLength = name.IndexOf('.');
|
||||
string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength);
|
||||
|
||||
// We will search each parent scope of "relativeTo" looking for the
|
||||
// symbol.
|
||||
StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Chop off the last component of the scope.
|
||||
|
||||
int dotpos = scopeToTry.ToString().LastIndexOf(".");
|
||||
if (dotpos == -1)
|
||||
{
|
||||
result = FindSymbol<IDescriptor>(name);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
scopeToTry.Length = dotpos + 1;
|
||||
|
||||
// Append firstPart and try to find.
|
||||
scopeToTry.Append(firstPart);
|
||||
result = FindSymbol<IDescriptor>(scopeToTry.ToString());
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
if (firstPartLength != -1)
|
||||
{
|
||||
// We only found the first part of the symbol. Now look for
|
||||
// the whole thing. If this fails, we *don't* want to keep
|
||||
// searching parent scopes.
|
||||
scopeToTry.Length = dotpos + 1;
|
||||
scopeToTry.Append(name);
|
||||
result = FindSymbol<IDescriptor>(scopeToTry.ToString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Not found. Remove the name so we can try again.
|
||||
scopeToTry.Length = dotpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal class containing utility methods when working with descriptors.
|
||||
/// </summary>
|
||||
internal static class DescriptorUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to Func[TInput, int, TOutput] but usable in .NET 2.0. Only used to convert
|
||||
/// arrays.
|
||||
/// </summary>
|
||||
internal delegate TOutput IndexedConverter<TInput, TOutput>(TInput element, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given array into a read-only list, applying the specified conversion to
|
||||
/// each input element.
|
||||
/// </summary>
|
||||
internal static IList<TOutput> ConvertAndMakeReadOnly<TInput, TOutput>
|
||||
(IList<TInput> input, IndexedConverter<TInput, TOutput> converter)
|
||||
{
|
||||
TOutput[] array = new TOutput[input.Count];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
array[i] = converter(input[i], i);
|
||||
}
|
||||
return new ReadOnlyCollection<TOutput>(array);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when building descriptors fails because the source DescriptorProtos
|
||||
/// are not valid.
|
||||
/// </summary>
|
||||
public sealed class DescriptorValidationException : Exception
|
||||
{
|
||||
private readonly String name;
|
||||
private readonly string description;
|
||||
|
||||
/// <value>
|
||||
/// The full name of the descriptor where the error occurred.
|
||||
/// </value>
|
||||
public String ProblemSymbolName
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// A human-readable description of the error. (The Message property
|
||||
/// is made up of the descriptor's name and this description.)
|
||||
/// </value>
|
||||
public string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
internal DescriptorValidationException(IDescriptor problemDescriptor, string description) :
|
||||
base(problemDescriptor.FullName + ": " + description)
|
||||
{
|
||||
// Note that problemDescriptor may be partially uninitialized, so we
|
||||
// don't want to expose it directly to the user. So, we only provide
|
||||
// the name and the original proto.
|
||||
name = problemDescriptor.FullName;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
internal DescriptorValidationException(IDescriptor problemDescriptor, string description, Exception cause) :
|
||||
base(problemDescriptor.FullName + ": " + description, cause)
|
||||
{
|
||||
name = problemDescriptor.FullName;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptor for an enum type in a .proto file.
|
||||
/// </summary>
|
||||
public sealed class EnumDescriptor : DescriptorBase
|
||||
{
|
||||
private readonly EnumDescriptorProto proto;
|
||||
private readonly MessageDescriptor containingType;
|
||||
private readonly IList<EnumValueDescriptor> values;
|
||||
private readonly Type clrType;
|
||||
|
||||
internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, Type clrType)
|
||||
: base(file, file.ComputeFullName(parent, proto.Name), index)
|
||||
{
|
||||
this.proto = proto;
|
||||
this.clrType = clrType;
|
||||
containingType = parent;
|
||||
|
||||
if (proto.Value.Count == 0)
|
||||
{
|
||||
// We cannot allow enums with no values because this would mean there
|
||||
// would be no valid default value for fields of this type.
|
||||
throw new DescriptorValidationException(this, "Enums must contain at least one value.");
|
||||
}
|
||||
|
||||
values = DescriptorUtil.ConvertAndMakeReadOnly(proto.Value,
|
||||
(value, i) => new EnumValueDescriptor(value, file, this, i));
|
||||
|
||||
File.DescriptorPool.AddSymbol(this);
|
||||
}
|
||||
|
||||
internal EnumDescriptorProto Proto { get { return proto; } }
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name { get { return proto.Name; } }
|
||||
|
||||
internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
|
||||
{
|
||||
switch (fieldNumber)
|
||||
{
|
||||
case EnumDescriptorProto.ValueFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) Values;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CLR type for this enum. For generated code, this will be a CLR enum type.
|
||||
/// </summary>
|
||||
public Type ClrType { get { return clrType; } }
|
||||
|
||||
/// <value>
|
||||
/// If this is a nested type, get the outer descriptor, otherwise null.
|
||||
/// </value>
|
||||
public MessageDescriptor ContainingType
|
||||
{
|
||||
get { return containingType; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// An unmodifiable list of defined value descriptors for this enum.
|
||||
/// </value>
|
||||
public IList<EnumValueDescriptor> Values
|
||||
{
|
||||
get { return values; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an enum value by number. If multiple enum values have the
|
||||
/// same number, this returns the first defined value with that number.
|
||||
/// If there is no value for the given number, this returns <c>null</c>.
|
||||
/// </summary>
|
||||
public EnumValueDescriptor FindValueByNumber(int number)
|
||||
{
|
||||
return File.DescriptorPool.FindEnumValueByNumber(this, number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an enum value by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The unqualified name of the value (e.g. "FOO").</param>
|
||||
/// <returns>The value's descriptor, or null if not found.</returns>
|
||||
public EnumValueDescriptor FindValueByName(string name)
|
||||
{
|
||||
return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this enum.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>EnumOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public EnumOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value enum option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<EnumOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value enum option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptor for a single enum value within an enum in a .proto file.
|
||||
/// </summary>
|
||||
public sealed class EnumValueDescriptor : DescriptorBase
|
||||
{
|
||||
private readonly EnumDescriptor enumDescriptor;
|
||||
private readonly EnumValueDescriptorProto proto;
|
||||
|
||||
internal EnumValueDescriptor(EnumValueDescriptorProto proto, FileDescriptor file,
|
||||
EnumDescriptor parent, int index)
|
||||
: base(file, parent.FullName + "." + proto.Name, index)
|
||||
{
|
||||
this.proto = proto;
|
||||
enumDescriptor = parent;
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
file.DescriptorPool.AddEnumValueByNumber(this);
|
||||
}
|
||||
|
||||
internal EnumValueDescriptorProto Proto { get { return proto; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the enum value described by this object.
|
||||
/// </summary>
|
||||
public override string Name { get { return proto.Name; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number associated with this enum value.
|
||||
/// </summary>
|
||||
public int Number { get { return Proto.Number; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the enum descriptor that this value is part of.
|
||||
/// </summary>
|
||||
public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this enum value.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>EnumValueOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public EnumValueOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value enum value option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<EnumValueOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value enum value option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumValueOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
internal sealed class ExtensionAccessor : IFieldAccessor
|
||||
{
|
||||
private readonly Extension extension;
|
||||
private readonly ReflectionUtil.IExtensionReflectionHelper helper;
|
||||
|
||||
internal ExtensionAccessor(FieldDescriptor descriptor)
|
||||
{
|
||||
Descriptor = descriptor;
|
||||
extension = descriptor.Extension;
|
||||
helper = ReflectionUtil.CreateExtensionHelper(extension);
|
||||
}
|
||||
|
||||
public FieldDescriptor Descriptor { get; }
|
||||
|
||||
public void Clear(IMessage message)
|
||||
{
|
||||
helper.ClearExtension(message);
|
||||
}
|
||||
|
||||
public bool HasValue(IMessage message)
|
||||
{
|
||||
return helper.HasExtension(message);
|
||||
}
|
||||
|
||||
public object GetValue(IMessage message)
|
||||
{
|
||||
return helper.GetExtension(message);
|
||||
}
|
||||
|
||||
public void SetValue(IMessage message, object value)
|
||||
{
|
||||
helper.SetExtension(message, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection to simplify retrieving the descriptors of extensions in a descriptor for a message
|
||||
/// </summary>
|
||||
public sealed class ExtensionCollection
|
||||
{
|
||||
private IDictionary<MessageDescriptor, IList<FieldDescriptor>> extensionsByTypeInDeclarationOrder;
|
||||
private IDictionary<MessageDescriptor, IList<FieldDescriptor>> extensionsByTypeInNumberOrder;
|
||||
|
||||
internal ExtensionCollection(FileDescriptor file, Extension[] extensions)
|
||||
{
|
||||
UnorderedExtensions = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
file.Proto.Extension,
|
||||
(extension, i) => {
|
||||
if (extensions?.Length != 0)
|
||||
{
|
||||
return new FieldDescriptor(extension, file, null, i, null, extensions?[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FieldDescriptor(extension, file, null, i, null, null); // return null if there's no extensions in this array for old code-gen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal ExtensionCollection(MessageDescriptor message, Extension[] extensions)
|
||||
{
|
||||
UnorderedExtensions = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
message.Proto.Extension,
|
||||
(extension, i) => {
|
||||
if (extensions?.Length != 0)
|
||||
{
|
||||
return new FieldDescriptor(extension, message.File, message, i, null, extensions?[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FieldDescriptor(extension, message.File, message, i, null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a readonly list of all the extensions defined in this type in
|
||||
/// the order they were defined in the source .proto file
|
||||
/// </summary>
|
||||
public IList<FieldDescriptor> UnorderedExtensions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a readonly list of all the extensions define in this type that extend
|
||||
/// the provided descriptor type in the order they were defined in the source .proto file
|
||||
/// </summary>
|
||||
public IList<FieldDescriptor> GetExtensionsInDeclarationOrder(MessageDescriptor descriptor)
|
||||
{
|
||||
return extensionsByTypeInDeclarationOrder[descriptor];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a readonly list of all the extensions define in this type that extend
|
||||
/// the provided descriptor type in accending field order
|
||||
/// </summary>
|
||||
public IList<FieldDescriptor> GetExtensionsInNumberOrder(MessageDescriptor descriptor)
|
||||
{
|
||||
return extensionsByTypeInNumberOrder[descriptor];
|
||||
}
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
Dictionary<MessageDescriptor, IList<FieldDescriptor>> declarationOrder = new Dictionary<MessageDescriptor, IList<FieldDescriptor>>();
|
||||
foreach (FieldDescriptor descriptor in UnorderedExtensions)
|
||||
{
|
||||
descriptor.CrossLink();
|
||||
|
||||
IList<FieldDescriptor> list;
|
||||
if (!declarationOrder.TryGetValue(descriptor.ExtendeeType, out list))
|
||||
{
|
||||
list = new List<FieldDescriptor>();
|
||||
declarationOrder.Add(descriptor.ExtendeeType, list);
|
||||
}
|
||||
|
||||
list.Add(descriptor);
|
||||
}
|
||||
|
||||
extensionsByTypeInDeclarationOrder = declarationOrder
|
||||
.ToDictionary(kvp => kvp.Key, kvp => (IList<FieldDescriptor>)new ReadOnlyCollection<FieldDescriptor>(kvp.Value));
|
||||
extensionsByTypeInNumberOrder = declarationOrder
|
||||
.ToDictionary(kvp => kvp.Key, kvp => (IList<FieldDescriptor>)new ReadOnlyCollection<FieldDescriptor>(kvp.Value.OrderBy(field => field.FieldNumber).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for field accessors.
|
||||
/// </summary>
|
||||
internal abstract class FieldAccessorBase : IFieldAccessor
|
||||
{
|
||||
private readonly Func<IMessage, object> getValueDelegate;
|
||||
private readonly FieldDescriptor descriptor;
|
||||
|
||||
internal FieldAccessorBase(PropertyInfo property, FieldDescriptor descriptor)
|
||||
{
|
||||
this.descriptor = descriptor;
|
||||
getValueDelegate = ReflectionUtil.CreateFuncIMessageObject(property.GetGetMethod());
|
||||
}
|
||||
|
||||
public FieldDescriptor Descriptor { get { return descriptor; } }
|
||||
|
||||
public object GetValue(IMessage message)
|
||||
{
|
||||
return getValueDelegate(message);
|
||||
}
|
||||
|
||||
public abstract bool HasValue(IMessage message);
|
||||
public abstract void Clear(IMessage message);
|
||||
public abstract void SetValue(IMessage message, object value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,455 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptor for a field or extension within a message in a .proto file.
|
||||
/// </summary>
|
||||
public sealed class FieldDescriptor : DescriptorBase, IComparable<FieldDescriptor>
|
||||
{
|
||||
private EnumDescriptor enumType;
|
||||
private MessageDescriptor extendeeType;
|
||||
private MessageDescriptor messageType;
|
||||
private FieldType fieldType;
|
||||
private readonly string propertyName; // Annoyingly, needed in Crosslink.
|
||||
private IFieldAccessor accessor;
|
||||
|
||||
/// <summary>
|
||||
/// Get the field's containing message type, or <c>null</c> if it is a field defined at the top level of a file as an extension.
|
||||
/// </summary>
|
||||
public MessageDescriptor ContainingType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the oneof containing this field, or <c>null</c> if it is not part of a oneof.
|
||||
/// </summary>
|
||||
public OneofDescriptor ContainingOneof { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the oneof containing this field if it's a "real" oneof, or <c>null</c> if either this
|
||||
/// field is not part of a oneof, or the oneof is synthetic.
|
||||
/// </summary>
|
||||
public OneofDescriptor RealContainingOneof => ContainingOneof?.IsSynthetic == false ? ContainingOneof : null;
|
||||
|
||||
/// <summary>
|
||||
/// The effective JSON name for this field. This is usually the lower-camel-cased form of the field name,
|
||||
/// but can be overridden using the <c>json_name</c> option in the .proto file.
|
||||
/// </summary>
|
||||
public string JsonName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this field supports presence, either implicitly (e.g. due to it being a message
|
||||
/// type field) or explicitly via Has/Clear members. If this returns true, it is safe to call
|
||||
/// <see cref="IFieldAccessor.Clear(IMessage)"/> and <see cref="IFieldAccessor.HasValue(IMessage)"/>
|
||||
/// on this field's accessor with a suitable message.
|
||||
/// </summary>
|
||||
public bool HasPresence =>
|
||||
Extension != null ? !Extension.IsRepeated
|
||||
: IsRepeated ? false
|
||||
: IsMap ? false
|
||||
: FieldType == FieldType.Message ? true
|
||||
// This covers "real oneof members" and "proto3 optional fields"
|
||||
: ContainingOneof != null ? true
|
||||
: File.Syntax == Syntax.Proto2;
|
||||
|
||||
internal FieldDescriptorProto Proto { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An extension identifier for this field, or <c>null</c> if this field isn't an extension.
|
||||
/// </summary>
|
||||
public Extension Extension { get; }
|
||||
|
||||
internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
|
||||
MessageDescriptor parent, int index, string propertyName, Extension extension)
|
||||
: base(file, file.ComputeFullName(parent, proto.Name), index)
|
||||
{
|
||||
Proto = proto;
|
||||
if (proto.Type != 0)
|
||||
{
|
||||
fieldType = GetFieldTypeFromProtoType(proto.Type);
|
||||
}
|
||||
|
||||
if (FieldNumber <= 0)
|
||||
{
|
||||
throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
|
||||
}
|
||||
ContainingType = parent;
|
||||
if (proto.HasOneofIndex)
|
||||
{
|
||||
if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
|
||||
{
|
||||
throw new DescriptorValidationException(this,
|
||||
$"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}");
|
||||
}
|
||||
ContainingOneof = parent.Oneofs[proto.OneofIndex];
|
||||
}
|
||||
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
// We can't create the accessor until we've cross-linked, unfortunately, as we
|
||||
// may not know whether the type of the field is a map or not. Remember the property name
|
||||
// for later.
|
||||
// We could trust the generated code and check whether the type of the property is
|
||||
// a MapField, but that feels a tad nasty.
|
||||
this.propertyName = propertyName;
|
||||
Extension = extension;
|
||||
JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name => Proto.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the accessor for this field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// While a <see cref="FieldDescriptor"/> describes the field, it does not provide
|
||||
/// any way of obtaining or changing the value of the field within a specific message;
|
||||
/// that is the responsibility of the accessor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In descriptors for generated code, the value returned by this property will be non-null for all
|
||||
/// regular fields. However, if a message containing a map field is introspected, the list of nested messages will include
|
||||
/// an auto-generated nested key/value pair message for the field. This is not represented in any
|
||||
/// generated type, and the value of the map field itself is represented by a dictionary in the
|
||||
/// reflection API. There are never instances of those "hidden" messages, so no accessor is provided
|
||||
/// and this property will return null.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In dynamically loaded descriptors, the value returned by this property will current be null;
|
||||
/// if and when dynamic messages are supported, it will return a suitable accessor to work with
|
||||
/// them.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public IFieldAccessor Accessor => accessor;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a field type as included in the .proto file to a FieldType.
|
||||
/// </summary>
|
||||
private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case FieldDescriptorProto.Types.Type.Double:
|
||||
return FieldType.Double;
|
||||
case FieldDescriptorProto.Types.Type.Float:
|
||||
return FieldType.Float;
|
||||
case FieldDescriptorProto.Types.Type.Int64:
|
||||
return FieldType.Int64;
|
||||
case FieldDescriptorProto.Types.Type.Uint64:
|
||||
return FieldType.UInt64;
|
||||
case FieldDescriptorProto.Types.Type.Int32:
|
||||
return FieldType.Int32;
|
||||
case FieldDescriptorProto.Types.Type.Fixed64:
|
||||
return FieldType.Fixed64;
|
||||
case FieldDescriptorProto.Types.Type.Fixed32:
|
||||
return FieldType.Fixed32;
|
||||
case FieldDescriptorProto.Types.Type.Bool:
|
||||
return FieldType.Bool;
|
||||
case FieldDescriptorProto.Types.Type.String:
|
||||
return FieldType.String;
|
||||
case FieldDescriptorProto.Types.Type.Group:
|
||||
return FieldType.Group;
|
||||
case FieldDescriptorProto.Types.Type.Message:
|
||||
return FieldType.Message;
|
||||
case FieldDescriptorProto.Types.Type.Bytes:
|
||||
return FieldType.Bytes;
|
||||
case FieldDescriptorProto.Types.Type.Uint32:
|
||||
return FieldType.UInt32;
|
||||
case FieldDescriptorProto.Types.Type.Enum:
|
||||
return FieldType.Enum;
|
||||
case FieldDescriptorProto.Types.Type.Sfixed32:
|
||||
return FieldType.SFixed32;
|
||||
case FieldDescriptorProto.Types.Type.Sfixed64:
|
||||
return FieldType.SFixed64;
|
||||
case FieldDescriptorProto.Types.Type.Sint32:
|
||||
return FieldType.SInt32;
|
||||
case FieldDescriptorProto.Types.Type.Sint64:
|
||||
return FieldType.SInt64;
|
||||
default:
|
||||
throw new ArgumentException("Invalid type specified");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this field is a required field; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsRequired => Proto.Label == FieldDescriptorProto.Types.Label.Required;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsPacked
|
||||
{
|
||||
get
|
||||
{
|
||||
if (File.Syntax != Syntax.Proto3)
|
||||
{
|
||||
return Proto.Options?.Packed ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !Proto.Options.HasPacked || Proto.Options.Packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this field extends another message type; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsExtension => Proto.HasExtendee;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of the field.
|
||||
/// </summary>
|
||||
public FieldType FieldType => fieldType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the field number declared in the proto file.
|
||||
/// </summary>
|
||||
public int FieldNumber => Proto.Number;
|
||||
|
||||
/// <summary>
|
||||
/// Compares this descriptor with another one, ordering in "canonical" order
|
||||
/// which simply means ascending order by field number. <paramref name="other"/>
|
||||
/// must be a field of the same type, i.e. the <see cref="ContainingType"/> of
|
||||
/// both fields must be the same.
|
||||
/// </summary>
|
||||
public int CompareTo(FieldDescriptor other)
|
||||
{
|
||||
if (other.ContainingType != ContainingType)
|
||||
{
|
||||
throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " +
|
||||
"for fields of the same message type.");
|
||||
}
|
||||
return FieldNumber - other.FieldNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For enum fields, returns the field's type.
|
||||
/// </summary>
|
||||
public EnumDescriptor EnumType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fieldType != FieldType.Enum)
|
||||
{
|
||||
throw new InvalidOperationException("EnumType is only valid for enum fields.");
|
||||
}
|
||||
return enumType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For embedded message and group fields, returns the field's type.
|
||||
/// </summary>
|
||||
public MessageDescriptor MessageType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fieldType != FieldType.Message && fieldType != FieldType.Group)
|
||||
{
|
||||
throw new InvalidOperationException("MessageType is only valid for message or group fields.");
|
||||
}
|
||||
return messageType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For extension fields, returns the extended type
|
||||
/// </summary>
|
||||
public MessageDescriptor ExtendeeType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Proto.HasExtendee)
|
||||
{
|
||||
throw new InvalidOperationException("ExtendeeType is only valid for extension fields.");
|
||||
}
|
||||
return extendeeType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this field.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>FieldOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public FieldOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value field option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<FieldOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value field option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<FieldOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up and cross-link all field types etc.
|
||||
/// </summary>
|
||||
internal void CrossLink()
|
||||
{
|
||||
if (Proto.HasTypeName)
|
||||
{
|
||||
IDescriptor typeDescriptor =
|
||||
File.DescriptorPool.LookupSymbol(Proto.TypeName, this);
|
||||
|
||||
if (Proto.HasType)
|
||||
{
|
||||
// Choose field type based on symbol.
|
||||
if (typeDescriptor is MessageDescriptor)
|
||||
{
|
||||
fieldType = FieldType.Message;
|
||||
}
|
||||
else if (typeDescriptor is EnumDescriptor)
|
||||
{
|
||||
fieldType = FieldType.Enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldType == FieldType.Message || fieldType == FieldType.Group)
|
||||
{
|
||||
if (!(typeDescriptor is MessageDescriptor))
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
|
||||
}
|
||||
messageType = (MessageDescriptor) typeDescriptor;
|
||||
|
||||
if (Proto.HasDefaultValue)
|
||||
{
|
||||
throw new DescriptorValidationException(this, "Messages can't have default values.");
|
||||
}
|
||||
}
|
||||
else if (fieldType == FieldType.Enum)
|
||||
{
|
||||
if (!(typeDescriptor is EnumDescriptor))
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
|
||||
}
|
||||
enumType = (EnumDescriptor) typeDescriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
|
||||
{
|
||||
throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Proto.HasExtendee)
|
||||
{
|
||||
extendeeType = File.DescriptorPool.LookupSymbol(Proto.Extendee, this) as MessageDescriptor;
|
||||
}
|
||||
|
||||
// Note: no attempt to perform any default value parsing
|
||||
|
||||
File.DescriptorPool.AddFieldByNumber(this);
|
||||
|
||||
if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
|
||||
{
|
||||
throw new DescriptorValidationException(this, "MessageSet format is not supported.");
|
||||
}
|
||||
accessor = CreateAccessor();
|
||||
}
|
||||
|
||||
private IFieldAccessor CreateAccessor()
|
||||
{
|
||||
if (Extension != null)
|
||||
{
|
||||
return new ExtensionAccessor(this);
|
||||
}
|
||||
|
||||
// If we're given no property name, that's because we really don't want an accessor.
|
||||
// This could be because it's a map message, or it could be that we're loading a FileDescriptor dynamically.
|
||||
// TODO: Support dynamic messages.
|
||||
if (propertyName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var property = ContainingType.ClrType.GetProperty(propertyName);
|
||||
if (property == null)
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
|
||||
}
|
||||
return IsMap ? new MapFieldAccessor(property, this)
|
||||
: IsRepeated ? new RepeatedFieldAccessor(property, this)
|
||||
: (IFieldAccessor) new SingleFieldAccessor(property, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of all the possible field types.
|
||||
/// </summary>
|
||||
public enum FieldType
|
||||
{
|
||||
/// <summary>
|
||||
/// The <c>double</c> field type.
|
||||
/// </summary>
|
||||
Double,
|
||||
/// <summary>
|
||||
/// The <c>float</c> field type.
|
||||
/// </summary>
|
||||
Float,
|
||||
/// <summary>
|
||||
/// The <c>int64</c> field type.
|
||||
/// </summary>
|
||||
Int64,
|
||||
/// <summary>
|
||||
/// The <c>uint64</c> field type.
|
||||
/// </summary>
|
||||
UInt64,
|
||||
/// <summary>
|
||||
/// The <c>int32</c> field type.
|
||||
/// </summary>
|
||||
Int32,
|
||||
/// <summary>
|
||||
/// The <c>fixed64</c> field type.
|
||||
/// </summary>
|
||||
Fixed64,
|
||||
/// <summary>
|
||||
/// The <c>fixed32</c> field type.
|
||||
/// </summary>
|
||||
Fixed32,
|
||||
/// <summary>
|
||||
/// The <c>bool</c> field type.
|
||||
/// </summary>
|
||||
Bool,
|
||||
/// <summary>
|
||||
/// The <c>string</c> field type.
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// The field type used for groups.
|
||||
/// </summary>
|
||||
Group,
|
||||
/// <summary>
|
||||
/// The field type used for message fields.
|
||||
/// </summary>
|
||||
Message,
|
||||
/// <summary>
|
||||
/// The <c>bytes</c> field type.
|
||||
/// </summary>
|
||||
Bytes,
|
||||
/// <summary>
|
||||
/// The <c>uint32</c> field type.
|
||||
/// </summary>
|
||||
UInt32,
|
||||
/// <summary>
|
||||
/// The <c>sfixed32</c> field type.
|
||||
/// </summary>
|
||||
SFixed32,
|
||||
/// <summary>
|
||||
/// The <c>sfixed64</c> field type.
|
||||
/// </summary>
|
||||
SFixed64,
|
||||
/// <summary>
|
||||
/// The <c>sint32</c> field type.
|
||||
/// </summary>
|
||||
SInt32,
|
||||
/// <summary>
|
||||
/// The <c>sint64</c> field type.
|
||||
/// </summary>
|
||||
SInt64,
|
||||
/// <summary>
|
||||
/// The field type used for enum fields.
|
||||
/// </summary>
|
||||
Enum
|
||||
}
|
||||
}
|
|
@ -0,0 +1,608 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using LC.Google.Protobuf.WellKnownTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using static LC.Google.Protobuf.Reflection.SourceCodeInfo.Types;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// The syntax of a .proto file
|
||||
/// </summary>
|
||||
public enum Syntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Proto2 syntax
|
||||
/// </summary>
|
||||
Proto2,
|
||||
/// <summary>
|
||||
/// Proto3 syntax
|
||||
/// </summary>
|
||||
Proto3,
|
||||
/// <summary>
|
||||
/// An unknown declared syntax
|
||||
/// </summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a .proto file, including everything defined within.
|
||||
/// IDescriptor is implemented such that the File property returns this descriptor,
|
||||
/// and the FullName is the same as the Name.
|
||||
/// </summary>
|
||||
public sealed class FileDescriptor : IDescriptor
|
||||
{
|
||||
// Prevent linker failures when using IL2CPP with the well-known types.
|
||||
static FileDescriptor()
|
||||
{
|
||||
ForceReflectionInitialization<Syntax>();
|
||||
ForceReflectionInitialization<NullValue>();
|
||||
ForceReflectionInitialization<Field.Types.Cardinality>();
|
||||
ForceReflectionInitialization<Field.Types.Kind>();
|
||||
ForceReflectionInitialization<Value.KindOneofCase>();
|
||||
}
|
||||
|
||||
private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations;
|
||||
|
||||
private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
|
||||
{
|
||||
SerializedData = descriptorData;
|
||||
DescriptorPool = pool;
|
||||
Proto = proto;
|
||||
Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList());
|
||||
|
||||
PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies);
|
||||
|
||||
pool.AddPackage(Package, this);
|
||||
|
||||
MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType,
|
||||
(message, index) =>
|
||||
new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index]));
|
||||
|
||||
EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType,
|
||||
(enumType, index) =>
|
||||
new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index]));
|
||||
|
||||
Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
|
||||
(service, index) =>
|
||||
new ServiceDescriptor(service, this, index));
|
||||
|
||||
Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
|
||||
|
||||
declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
if (!proto.HasSyntax || proto.Syntax == "proto2")
|
||||
{
|
||||
Syntax = Syntax.Proto2;
|
||||
}
|
||||
else if (proto.Syntax == "proto3")
|
||||
{
|
||||
Syntax = Syntax.Proto3;
|
||||
}
|
||||
else
|
||||
{
|
||||
Syntax = Syntax.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap()
|
||||
{
|
||||
var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>();
|
||||
foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>())
|
||||
{
|
||||
var descriptor = FindDescriptorForPath(location.Path);
|
||||
if (descriptor != null)
|
||||
{
|
||||
dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location);
|
||||
}
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private IDescriptor FindDescriptorForPath(IList<int> path)
|
||||
{
|
||||
// All complete declarations have an even, non-empty path length
|
||||
// (There can be an empty path for a descriptor declaration, but that can't have any comments,
|
||||
// so we currently ignore it.)
|
||||
if (path.Count == 0 || (path.Count & 1) != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]);
|
||||
DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]);
|
||||
|
||||
for (int i = 2; current != null && i < path.Count; i += 2)
|
||||
{
|
||||
var list = current.GetNestedDescriptorListForField(path[i]);
|
||||
current = GetDescriptorFromList(list, path[i + 1]);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)
|
||||
{
|
||||
// This is fine: it may be a newer version of protobuf than we understand, with a new descriptor
|
||||
// field.
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// We *could* return null to silently continue, but this is basically data corruption.
|
||||
if (index < 0 || index >= list.Count)
|
||||
{
|
||||
// We don't have much extra information to give at this point unfortunately. If this becomes a problem,
|
||||
// we can pass in the complete path and report that and the file name.
|
||||
throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range");
|
||||
}
|
||||
return list[index];
|
||||
}
|
||||
|
||||
private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
|
||||
{
|
||||
switch (fieldNumber)
|
||||
{
|
||||
case FileDescriptorProto.ServiceFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) Services;
|
||||
case FileDescriptorProto.MessageTypeFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) MessageTypes;
|
||||
case FileDescriptorProto.EnumTypeFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) EnumTypes;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor)
|
||||
{
|
||||
DescriptorDeclaration declaration;
|
||||
declarations.Value.TryGetValue(descriptor, out declaration);
|
||||
return declaration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the full name of a descriptor within this file, with an optional parent message.
|
||||
/// </summary>
|
||||
internal string ComputeFullName(MessageDescriptor parent, string name)
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
return parent.FullName + "." + name;
|
||||
}
|
||||
if (Package.Length > 0)
|
||||
{
|
||||
return Package + "." + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts public dependencies from direct dependencies. This is a static method despite its
|
||||
/// first parameter, as the value we're in the middle of constructing is only used for exceptions.
|
||||
/// </summary>
|
||||
private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies)
|
||||
{
|
||||
var nameToFileMap = dependencies.ToDictionary(file => file.Name);
|
||||
var publicDependencies = new List<FileDescriptor>();
|
||||
for (int i = 0; i < proto.PublicDependency.Count; i++)
|
||||
{
|
||||
int index = proto.PublicDependency[i];
|
||||
if (index < 0 || index >= proto.Dependency.Count)
|
||||
{
|
||||
throw new DescriptorValidationException(@this, "Invalid public dependency index.");
|
||||
}
|
||||
string name = proto.Dependency[index];
|
||||
FileDescriptor file;
|
||||
if (!nameToFileMap.TryGetValue(name, out file))
|
||||
{
|
||||
if (!allowUnknownDependencies)
|
||||
{
|
||||
throw new DescriptorValidationException(@this, "Invalid public dependency: " + name);
|
||||
}
|
||||
// Ignore unknown dependencies.
|
||||
}
|
||||
else
|
||||
{
|
||||
publicDependencies.Add(file);
|
||||
}
|
||||
}
|
||||
return new ReadOnlyCollection<FileDescriptor>(publicDependencies);
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The descriptor in its protocol message representation.
|
||||
/// </value>
|
||||
internal FileDescriptorProto Proto { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The syntax of the file
|
||||
/// </summary>
|
||||
public Syntax Syntax { get; }
|
||||
|
||||
/// <value>
|
||||
/// The file name.
|
||||
/// </value>
|
||||
public string Name => Proto.Name;
|
||||
|
||||
/// <summary>
|
||||
/// The package as declared in the .proto file. This may or may not
|
||||
/// be equivalent to the .NET namespace of the generated classes.
|
||||
/// </summary>
|
||||
public string Package => Proto.Package;
|
||||
|
||||
/// <value>
|
||||
/// Unmodifiable list of top-level message types declared in this file.
|
||||
/// </value>
|
||||
public IList<MessageDescriptor> MessageTypes { get; }
|
||||
|
||||
/// <value>
|
||||
/// Unmodifiable list of top-level enum types declared in this file.
|
||||
/// </value>
|
||||
public IList<EnumDescriptor> EnumTypes { get; }
|
||||
|
||||
/// <value>
|
||||
/// Unmodifiable list of top-level services declared in this file.
|
||||
/// </value>
|
||||
public IList<ServiceDescriptor> Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Unmodifiable list of top-level extensions declared in this file.
|
||||
/// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
|
||||
/// if this descriptor was generated using a version of protoc that did not fully
|
||||
/// support extensions in C#.
|
||||
/// </summary>
|
||||
public ExtensionCollection Extensions { get; }
|
||||
|
||||
/// <value>
|
||||
/// Unmodifiable list of this file's dependencies (imports).
|
||||
/// </value>
|
||||
public IList<FileDescriptor> Dependencies { get; }
|
||||
|
||||
/// <value>
|
||||
/// Unmodifiable list of this file's public dependencies (public imports).
|
||||
/// </value>
|
||||
public IList<FileDescriptor> PublicDependencies { get; }
|
||||
|
||||
/// <value>
|
||||
/// The original serialized binary form of this descriptor.
|
||||
/// </value>
|
||||
public ByteString SerializedData { get; }
|
||||
|
||||
/// <value>
|
||||
/// Implementation of IDescriptor.FullName - just returns the same as Name.
|
||||
/// </value>
|
||||
string IDescriptor.FullName => Name;
|
||||
|
||||
/// <value>
|
||||
/// Implementation of IDescriptor.File - just returns this descriptor.
|
||||
/// </value>
|
||||
FileDescriptor IDescriptor.File => this;
|
||||
|
||||
/// <value>
|
||||
/// Pool containing symbol descriptors.
|
||||
/// </value>
|
||||
internal DescriptorPool DescriptorPool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types.
|
||||
/// </summary>
|
||||
/// <param name="name">The unqualified type name to look for.</param>
|
||||
/// <typeparam name="T">The type of descriptor to look for</typeparam>
|
||||
/// <returns>The type's descriptor, or null if not found.</returns>
|
||||
public T FindTypeByName<T>(String name)
|
||||
where T : class, IDescriptor
|
||||
{
|
||||
// Don't allow looking up nested types. This will make optimization
|
||||
// easier later.
|
||||
if (name.IndexOf('.') != -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (Package.Length > 0)
|
||||
{
|
||||
name = Package + "." + name;
|
||||
}
|
||||
T result = DescriptorPool.FindSymbol<T>(name);
|
||||
if (result != null && result.File == this)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a FileDescriptor from its protocol buffer representation.
|
||||
/// </summary>
|
||||
/// <param name="descriptorData">The original serialized descriptor data.
|
||||
/// We have only limited proto2 support, so serializing FileDescriptorProto
|
||||
/// would not necessarily give us this.</param>
|
||||
/// <param name="proto">The protocol message form of the FileDescriptor.</param>
|
||||
/// <param name="dependencies">FileDescriptors corresponding to all of the
|
||||
/// file's dependencies, in the exact order listed in the .proto file. May be null,
|
||||
/// in which case it is treated as an empty array.</param>
|
||||
/// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param>
|
||||
/// <param name="generatedCodeInfo">Details about generated code, for the purposes of reflection.</param>
|
||||
/// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not
|
||||
/// a valid descriptor. This can occur for a number of reasons, such as a field
|
||||
/// having an undefined type or because two messages were defined with the same name.</exception>
|
||||
private static FileDescriptor BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
|
||||
{
|
||||
// Building descriptors involves two steps: translating and linking.
|
||||
// In the translation step (implemented by FileDescriptor's
|
||||
// constructor), we build an object tree mirroring the
|
||||
// FileDescriptorProto's tree and put all of the descriptors into the
|
||||
// DescriptorPool's lookup tables. In the linking step, we look up all
|
||||
// type references in the DescriptorPool, so that, for example, a
|
||||
// FieldDescriptor for an embedded message contains a pointer directly
|
||||
// to the Descriptor for that message's type. We also detect undefined
|
||||
// types in the linking step.
|
||||
if (dependencies == null)
|
||||
{
|
||||
dependencies = new FileDescriptor[0];
|
||||
}
|
||||
|
||||
DescriptorPool pool = new DescriptorPool(dependencies);
|
||||
FileDescriptor result = new FileDescriptor(descriptorData, proto, dependencies, pool, allowUnknownDependencies, generatedCodeInfo);
|
||||
|
||||
// Validate that the dependencies we've been passed (as FileDescriptors) are actually the ones we
|
||||
// need.
|
||||
if (dependencies.Length != proto.Dependency.Count)
|
||||
{
|
||||
throw new DescriptorValidationException(
|
||||
result,
|
||||
"Dependencies passed to FileDescriptor.BuildFrom() don't match " +
|
||||
"those listed in the FileDescriptorProto.");
|
||||
}
|
||||
|
||||
result.CrossLink();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CrossLink()
|
||||
{
|
||||
foreach (MessageDescriptor message in MessageTypes)
|
||||
{
|
||||
message.CrossLink();
|
||||
}
|
||||
|
||||
foreach (ServiceDescriptor service in Services)
|
||||
{
|
||||
service.CrossLink();
|
||||
}
|
||||
|
||||
Extensions.CrossLink();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a descriptor for generated code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is only designed to be used by the results of generating code with protoc,
|
||||
/// which creates the appropriate dependencies etc. It has to be public because the generated
|
||||
/// code is "external", but should not be called directly by end users.
|
||||
/// </remarks>
|
||||
public static FileDescriptor FromGeneratedCode(
|
||||
byte[] descriptorData,
|
||||
FileDescriptor[] dependencies,
|
||||
GeneratedClrTypeInfo generatedCodeInfo)
|
||||
{
|
||||
ExtensionRegistry registry = new ExtensionRegistry();
|
||||
registry.AddRange(GetAllExtensions(dependencies, generatedCodeInfo));
|
||||
|
||||
FileDescriptorProto proto;
|
||||
try
|
||||
{
|
||||
proto = FileDescriptorProto.Parser.WithExtensionRegistry(registry).ParseFrom(descriptorData);
|
||||
}
|
||||
catch (InvalidProtocolBufferException e)
|
||||
{
|
||||
throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// When building descriptors for generated code, we allow unknown
|
||||
// dependencies by default.
|
||||
return BuildFrom(ByteString.CopyFrom(descriptorData), proto, dependencies, true, generatedCodeInfo);
|
||||
}
|
||||
catch (DescriptorValidationException e)
|
||||
{
|
||||
throw new ArgumentException($"Invalid embedded descriptor for \"{proto.Name}\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo)
|
||||
{
|
||||
return dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo));
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)
|
||||
{
|
||||
return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions));
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllDependedExtensions(FileDescriptor descriptor)
|
||||
{
|
||||
return descriptor.Extensions.UnorderedExtensions
|
||||
.Select(s => s.Extension)
|
||||
.Where(e => e != null)
|
||||
.Concat(descriptor.Dependencies.Concat(descriptor.PublicDependencies).SelectMany(GetAllDependedExtensions))
|
||||
.Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage));
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)
|
||||
{
|
||||
return descriptor.Extensions.UnorderedExtensions
|
||||
.Select(s => s.Extension)
|
||||
.Where(e => e != null)
|
||||
.Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given descriptor binary data into FileDescriptor objects.
|
||||
/// Note: reflection using the returned FileDescriptors is not currently supported.
|
||||
/// </summary>
|
||||
/// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any
|
||||
/// dependencies must come before the descriptor which depends on them. (If A depends on B, and B
|
||||
/// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible
|
||||
/// with the order in which protoc provides descriptors to plugins.</param>
|
||||
/// <param name="registry">The extension registry to use when parsing, or null if no extensions are required.</param>
|
||||
/// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns>
|
||||
public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData, ExtensionRegistry registry)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData));
|
||||
|
||||
var parser = FileDescriptorProto.Parser.WithExtensionRegistry(registry);
|
||||
|
||||
// TODO: See if we can build a single DescriptorPool instead of building lots of them.
|
||||
// This will all behave correctly, but it's less efficient than we'd like.
|
||||
var descriptors = new List<FileDescriptor>();
|
||||
var descriptorsByName = new Dictionary<string, FileDescriptor>();
|
||||
foreach (var data in descriptorData)
|
||||
{
|
||||
var proto = parser.ParseFrom(data);
|
||||
var dependencies = new List<FileDescriptor>();
|
||||
foreach (var dependencyName in proto.Dependency)
|
||||
{
|
||||
FileDescriptor dependency;
|
||||
if (!descriptorsByName.TryGetValue(dependencyName, out dependency))
|
||||
{
|
||||
throw new ArgumentException($"Dependency missing: {dependencyName}");
|
||||
}
|
||||
dependencies.Add(dependency);
|
||||
}
|
||||
var pool = new DescriptorPool(dependencies);
|
||||
FileDescriptor descriptor = new FileDescriptor(
|
||||
data, proto, dependencies, pool,
|
||||
allowUnknownDependencies: false, generatedCodeInfo: null);
|
||||
descriptor.CrossLink();
|
||||
descriptors.Add(descriptor);
|
||||
if (descriptorsByName.ContainsKey(descriptor.Name))
|
||||
{
|
||||
throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}");
|
||||
}
|
||||
descriptorsByName.Add(descriptor.Name, descriptor);
|
||||
}
|
||||
return new ReadOnlyCollection<FileDescriptor>(descriptors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given descriptor binary data into FileDescriptor objects.
|
||||
/// Note: reflection using the returned FileDescriptors is not currently supported.
|
||||
/// </summary>
|
||||
/// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any
|
||||
/// dependencies must come before the descriptor which depends on them. (If A depends on B, and B
|
||||
/// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible
|
||||
/// with the order in which protoc provides descriptors to plugins.</param>
|
||||
/// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns>
|
||||
public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData) =>
|
||||
BuildFromByteStrings(descriptorData, null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"FileDescriptor for {Name}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file descriptor for descriptor.proto.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for protos which take a direct dependency on <c>descriptor.proto</c>, typically for
|
||||
/// annotations. While <c>descriptor.proto</c> is a proto2 file, it is built into the Google.Protobuf
|
||||
/// runtime for reflection purposes. The messages are internal to the runtime as they would require
|
||||
/// proto2 semantics for full support, but the file descriptor is available via this property. The
|
||||
/// C# codegen in protoc automatically uses this property when it detects a dependency on <c>descriptor.proto</c>.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// The file descriptor for <c>descriptor.proto</c>.
|
||||
/// </value>
|
||||
public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this file.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>FileOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public FileOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value file option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<FileOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value file option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<FileOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs initialization for the given generic type argument.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated)
|
||||
/// to make calls into the reflection machinery of this library to express an intention to use that type
|
||||
/// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers
|
||||
/// attempting to determine which generic type arguments need to be handled will spot the code path and act
|
||||
/// accordingly.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type to force initialization for.</typeparam>
|
||||
public static void ForceReflectionInitialization<T>() => ReflectionUtil.ForceInitialize<T>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
#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
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Extra information provided by generated code when initializing a message or file descriptor.
|
||||
/// These are constructed as required, and are not long-lived. Hand-written code should
|
||||
/// never need to use this type.
|
||||
/// </summary>
|
||||
public sealed class GeneratedClrTypeInfo
|
||||
{
|
||||
private static readonly string[] EmptyNames = new string[0];
|
||||
private static readonly GeneratedClrTypeInfo[] EmptyCodeInfo = new GeneratedClrTypeInfo[0];
|
||||
private static readonly Extension[] EmptyExtensions = new Extension[0];
|
||||
|
||||
/// <summary>
|
||||
/// Irrelevant for file descriptors; the CLR type for the message for message descriptors.
|
||||
/// </summary>
|
||||
public Type ClrType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Irrelevant for file descriptors; the parser for message descriptors.
|
||||
/// </summary>
|
||||
public MessageParser Parser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Irrelevant for file descriptors; the CLR property names (in message descriptor field order)
|
||||
/// for fields in the message for message descriptors.
|
||||
/// </summary>
|
||||
public string[] PropertyNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The extensions defined within this file/message descriptor
|
||||
/// </summary>
|
||||
public Extension[] Extensions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Irrelevant for file descriptors; the CLR property "base" names (in message descriptor oneof order)
|
||||
/// for oneofs in the message for message descriptors. It is expected that for a oneof name of "Foo",
|
||||
/// there will be a "FooCase" property and a "ClearFoo" method.
|
||||
/// </summary>
|
||||
public string[] OneofNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The reflection information for types within this file/message descriptor. Elements may be null
|
||||
/// if there is no corresponding generated type, e.g. for map entry types.
|
||||
/// </summary>
|
||||
public GeneratedClrTypeInfo[] NestedTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The CLR types for enums within this file/message descriptor.
|
||||
/// </summary>
|
||||
public Type[] NestedEnums { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GeneratedClrTypeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names.
|
||||
/// Each array parameter may be null, to indicate a lack of values.
|
||||
/// The parameter order is designed to make it feasible to format the generated code readably.
|
||||
/// </summary>
|
||||
public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes)
|
||||
{
|
||||
NestedTypes = nestedTypes ?? EmptyCodeInfo;
|
||||
NestedEnums = nestedEnums ?? ReflectionUtil.EmptyTypes;
|
||||
ClrType = clrType;
|
||||
Extensions = extensions ?? EmptyExtensions;
|
||||
Parser = parser;
|
||||
PropertyNames = propertyNames ?? EmptyNames;
|
||||
OneofNames = oneofNames ?? EmptyNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GeneratedClrTypeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names.
|
||||
/// Each array parameter may be null, to indicate a lack of values.
|
||||
/// The parameter order is designed to make it feasible to format the generated code readably.
|
||||
/// </summary>
|
||||
public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes)
|
||||
: this(clrType, parser, propertyNames, oneofNames, nestedEnums, null, nestedTypes)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GeneratedClrTypeInfo for a file descriptor, with only types, enums, and extensions.
|
||||
/// </summary>
|
||||
public GeneratedClrTypeInfo(Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes)
|
||||
: this(null, null, null, null, nestedEnums, extensions, nestedTypes)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GeneratedClrTypeInfo for a file descriptor, with only types and enums.
|
||||
/// </summary>
|
||||
public GeneratedClrTypeInfo(Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes)
|
||||
: this(null, null, null, null, nestedEnums, nestedTypes)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface implemented by all descriptor types.
|
||||
/// </summary>
|
||||
public interface IDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the name of the entity (message, field etc) being described.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the fully-qualified name of the entity being described.
|
||||
/// </summary>
|
||||
string FullName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the descriptor for the .proto file that this entity is part of.
|
||||
/// </summary>
|
||||
FileDescriptor File { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows fields to be reflectively accessed.
|
||||
/// </summary>
|
||||
public interface IFieldAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the descriptor associated with this field.
|
||||
/// </summary>
|
||||
FieldDescriptor Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the field in the specified message. (For repeated fields,
|
||||
/// this clears the list.)
|
||||
/// </summary>
|
||||
void Clear(IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the field value. For repeated values, this will be an
|
||||
/// <see cref="IList"/> implementation. For map values, this will be an
|
||||
/// <see cref="IDictionary"/> implementation.
|
||||
/// </summary>
|
||||
object GetValue(IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the field in the specified message is set.
|
||||
/// For proto3 fields that aren't explicitly optional, this throws an <see cref="InvalidOperationException"/>
|
||||
/// </summary>
|
||||
bool HasValue(IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Mutator for single "simple" fields only.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Repeated fields are mutated by fetching the value and manipulating it as a list.
|
||||
/// Map fields are mutated by fetching the value and manipulating it as a dictionary.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">The field is not a "simple" field.</exception>
|
||||
void SetValue(IMessage message, object value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Accessor for map fields.
|
||||
/// </summary>
|
||||
internal sealed class MapFieldAccessor : FieldAccessorBase
|
||||
{
|
||||
internal MapFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Clear(IMessage message)
|
||||
{
|
||||
IDictionary list = (IDictionary) GetValue(message);
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
public override bool HasValue(IMessage message)
|
||||
{
|
||||
throw new InvalidOperationException("HasValue is not implemented for map fields");
|
||||
}
|
||||
|
||||
public override void SetValue(IMessage message, object value)
|
||||
{
|
||||
throw new InvalidOperationException("SetValue is not implemented for map fields");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,420 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
#if NET35
|
||||
// Needed for ReadOnlyDictionary, which does not exist in .NET 3.5
|
||||
using LC.Google.Protobuf.Collections;
|
||||
#endif
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a message type.
|
||||
/// </summary>
|
||||
public sealed class MessageDescriptor : DescriptorBase
|
||||
{
|
||||
private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
|
||||
{
|
||||
"google/protobuf/any.proto",
|
||||
"google/protobuf/api.proto",
|
||||
"google/protobuf/duration.proto",
|
||||
"google/protobuf/empty.proto",
|
||||
"google/protobuf/wrappers.proto",
|
||||
"google/protobuf/timestamp.proto",
|
||||
"google/protobuf/field_mask.proto",
|
||||
"google/protobuf/source_context.proto",
|
||||
"google/protobuf/struct.proto",
|
||||
"google/protobuf/type.proto",
|
||||
};
|
||||
|
||||
private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
|
||||
private readonly IList<FieldDescriptor> fieldsInNumberOrder;
|
||||
private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
|
||||
private Func<IMessage, bool> extensionSetIsInitialized;
|
||||
|
||||
internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
|
||||
: base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
|
||||
{
|
||||
Proto = proto;
|
||||
Parser = generatedCodeInfo?.Parser;
|
||||
ClrType = generatedCodeInfo?.ClrType;
|
||||
ContainingType = parent;
|
||||
|
||||
// If generatedCodeInfo is null, we just won't generate an accessor for any fields.
|
||||
Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
proto.OneofDecl,
|
||||
(oneof, index) =>
|
||||
new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index]));
|
||||
|
||||
int syntheticOneofCount = 0;
|
||||
foreach (var oneof in Oneofs)
|
||||
{
|
||||
if (oneof.IsSynthetic)
|
||||
{
|
||||
syntheticOneofCount++;
|
||||
}
|
||||
else if (syntheticOneofCount != 0)
|
||||
{
|
||||
throw new ArgumentException("All synthetic oneofs should come after real oneofs");
|
||||
}
|
||||
}
|
||||
RealOneofCount = Oneofs.Count - syntheticOneofCount;
|
||||
|
||||
NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
proto.NestedType,
|
||||
(type, index) =>
|
||||
new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index]));
|
||||
|
||||
EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
proto.EnumType,
|
||||
(type, index) =>
|
||||
new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index]));
|
||||
|
||||
Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
|
||||
|
||||
fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
|
||||
proto.Field,
|
||||
(field, index) =>
|
||||
new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index], null));
|
||||
fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
|
||||
// TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
|
||||
jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder);
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
Fields = new FieldCollection(this);
|
||||
}
|
||||
|
||||
private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
|
||||
{
|
||||
var map = new Dictionary<string, FieldDescriptor>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
map[field.Name] = field;
|
||||
map[field.JsonName] = field;
|
||||
}
|
||||
return new ReadOnlyDictionary<string, FieldDescriptor>(map);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name => Proto.Name;
|
||||
|
||||
internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
|
||||
{
|
||||
switch (fieldNumber)
|
||||
{
|
||||
case DescriptorProto.FieldFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
|
||||
case DescriptorProto.NestedTypeFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) NestedTypes;
|
||||
case DescriptorProto.EnumTypeFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) EnumTypes;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal DescriptorProto Proto { get; }
|
||||
|
||||
internal bool IsExtensionsInitialized(IMessage message)
|
||||
{
|
||||
if (Proto.ExtensionRange.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (extensionSetIsInitialized == null)
|
||||
{
|
||||
extensionSetIsInitialized = ReflectionUtil.CreateIsInitializedCaller(ClrType);
|
||||
}
|
||||
|
||||
return extensionSetIsInitialized(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CLR type used to represent message instances from this descriptor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value returned by this property will be non-null for all regular fields. However,
|
||||
/// if a message containing a map field is introspected, the list of nested messages will include
|
||||
/// an auto-generated nested key/value pair message for the field. This is not represented in any
|
||||
/// generated type, so this property will return null in such cases.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
|
||||
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
|
||||
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
|
||||
/// a wrapper type, and handle the result appropriately.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Type ClrType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A parser for this message type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
|
||||
/// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The value returned by this property will be non-null for all regular fields. However,
|
||||
/// if a message containing a map field is introspected, the list of nested messages will include
|
||||
/// an auto-generated nested key/value pair message for the field. No message parser object is created for
|
||||
/// such messages, so this property will return null in such cases.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
|
||||
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
|
||||
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
|
||||
/// a wrapper type, and handle the result appropriately.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public MessageParser Parser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
|
||||
/// </summary>
|
||||
internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
|
||||
/// with the addition of presence.
|
||||
/// </summary>
|
||||
internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
|
||||
|
||||
/// <value>
|
||||
/// If this is a nested type, get the outer descriptor, otherwise null.
|
||||
/// </value>
|
||||
public MessageDescriptor ContainingType { get; }
|
||||
|
||||
/// <value>
|
||||
/// A collection of fields, which can be retrieved by name or field number.
|
||||
/// </value>
|
||||
public FieldCollection Fields { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An unmodifiable list of extensions defined in this message's scope.
|
||||
/// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
|
||||
/// if they are declared in a file generated using a version of protoc that did not fully
|
||||
/// support extensions in C#.
|
||||
/// </summary>
|
||||
public ExtensionCollection Extensions { get; }
|
||||
|
||||
/// <value>
|
||||
/// An unmodifiable list of this message type's nested types.
|
||||
/// </value>
|
||||
public IList<MessageDescriptor> NestedTypes { get; }
|
||||
|
||||
/// <value>
|
||||
/// An unmodifiable list of this message type's enum types.
|
||||
/// </value>
|
||||
public IList<EnumDescriptor> EnumTypes { get; }
|
||||
|
||||
/// <value>
|
||||
/// An unmodifiable list of the "oneof" field collections in this message type.
|
||||
/// All "real" oneofs (where <see cref="OneofDescriptor.IsSynthetic"/> returns false)
|
||||
/// come before synthetic ones.
|
||||
/// </value>
|
||||
public IList<OneofDescriptor> Oneofs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of real "oneof" descriptors in this message type. Every element in <see cref="Oneofs"/>
|
||||
/// with an index less than this will have a <see cref="OneofDescriptor.IsSynthetic"/> property value
|
||||
/// of <c>false</c>; every element with an index greater than or equal to this will have a
|
||||
/// <see cref="OneofDescriptor.IsSynthetic"/> property value of <c>true</c>.
|
||||
/// </summary>
|
||||
public int RealOneofCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds a field by field name.
|
||||
/// </summary>
|
||||
/// <param name="name">The unqualified name of the field (e.g. "foo").</param>
|
||||
/// <returns>The field's descriptor, or null if not found.</returns>
|
||||
public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a field by field number.
|
||||
/// </summary>
|
||||
/// <param name="number">The field number within this message type.</param>
|
||||
/// <returns>The field's descriptor, or null if not found.</returns>
|
||||
public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a nested descriptor by name. The is valid for fields, nested
|
||||
/// message types, oneofs and enums.
|
||||
/// </summary>
|
||||
/// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
|
||||
/// <returns>The descriptor, or null if not found.</returns>
|
||||
public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
|
||||
File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this message.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>MessageOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public MessageOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value message option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<MessageOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value message option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up and cross-links all fields and nested types.
|
||||
/// </summary>
|
||||
internal void CrossLink()
|
||||
{
|
||||
foreach (MessageDescriptor message in NestedTypes)
|
||||
{
|
||||
message.CrossLink();
|
||||
}
|
||||
|
||||
foreach (FieldDescriptor field in fieldsInDeclarationOrder)
|
||||
{
|
||||
field.CrossLink();
|
||||
}
|
||||
|
||||
foreach (OneofDescriptor oneof in Oneofs)
|
||||
{
|
||||
oneof.CrossLink();
|
||||
}
|
||||
|
||||
Extensions.CrossLink();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection to simplify retrieving the field accessor for a particular field.
|
||||
/// </summary>
|
||||
public sealed class FieldCollection
|
||||
{
|
||||
private readonly MessageDescriptor messageDescriptor;
|
||||
|
||||
internal FieldCollection(MessageDescriptor messageDescriptor)
|
||||
{
|
||||
this.messageDescriptor = messageDescriptor;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Returns the fields in the message as an immutable list, in the order in which they
|
||||
/// are declared in the source .proto file.
|
||||
/// </value>
|
||||
public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
|
||||
|
||||
/// <value>
|
||||
/// Returns the fields in the message as an immutable list, in ascending field number
|
||||
/// order. Field numbers need not be contiguous, so there is no direct mapping from the
|
||||
/// index in the list to the field number; to retrieve a field by field number, it is better
|
||||
/// to use the <see cref="FieldCollection"/> indexer.
|
||||
/// </value>
|
||||
public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
|
||||
|
||||
// TODO: consider making this public in the future. (Being conservative for now...)
|
||||
|
||||
/// <value>
|
||||
/// Returns a read-only dictionary mapping the field names in this message as they're available
|
||||
/// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
|
||||
/// in the message would result two entries, one with a key <c>fooBar</c> and one with a key
|
||||
/// <c>foo_bar</c>, both referring to the same field.
|
||||
/// </value>
|
||||
internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the descriptor for the field with the given number.
|
||||
/// </summary>
|
||||
/// <param name="number">Number of the field to retrieve the descriptor for</param>
|
||||
/// <returns>The accessor for the given field</returns>
|
||||
/// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
|
||||
/// with the given number</exception>
|
||||
public FieldDescriptor this[int number]
|
||||
{
|
||||
get
|
||||
{
|
||||
var fieldDescriptor = messageDescriptor.FindFieldByNumber(number);
|
||||
if (fieldDescriptor == null)
|
||||
{
|
||||
throw new KeyNotFoundException("No such field number");
|
||||
}
|
||||
return fieldDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the descriptor for the field with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the field to retrieve the descriptor for</param>
|
||||
/// <returns>The descriptor for the given field</returns>
|
||||
/// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
|
||||
/// with the given name</exception>
|
||||
public FieldDescriptor this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
var fieldDescriptor = messageDescriptor.FindFieldByName(name);
|
||||
if (fieldDescriptor == null)
|
||||
{
|
||||
throw new KeyNotFoundException("No such field name");
|
||||
}
|
||||
return fieldDescriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a single method in a service.
|
||||
/// </summary>
|
||||
public sealed class MethodDescriptor : DescriptorBase
|
||||
{
|
||||
private readonly MethodDescriptorProto proto;
|
||||
private readonly ServiceDescriptor service;
|
||||
private MessageDescriptor inputType;
|
||||
private MessageDescriptor outputType;
|
||||
|
||||
/// <value>
|
||||
/// The service this method belongs to.
|
||||
/// </value>
|
||||
public ServiceDescriptor Service { get { return service; } }
|
||||
|
||||
/// <value>
|
||||
/// The method's input type.
|
||||
/// </value>
|
||||
public MessageDescriptor InputType { get { return inputType; } }
|
||||
|
||||
/// <value>
|
||||
/// The method's input type.
|
||||
/// </value>
|
||||
public MessageDescriptor OutputType { get { return outputType; } }
|
||||
|
||||
/// <value>
|
||||
/// Indicates if client streams multiple requests.
|
||||
/// </value>
|
||||
public bool IsClientStreaming { get { return proto.ClientStreaming; } }
|
||||
|
||||
/// <value>
|
||||
/// Indicates if server streams multiple responses.
|
||||
/// </value>
|
||||
public bool IsServerStreaming { get { return proto.ServerStreaming; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this method.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>MethodOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public MethodOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value method option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<MethodOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value method option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<MethodOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
|
||||
ServiceDescriptor parent, int index)
|
||||
: base(file, parent.FullName + "." + proto.Name, index)
|
||||
{
|
||||
this.proto = proto;
|
||||
service = parent;
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
}
|
||||
|
||||
internal MethodDescriptorProto Proto { get { return proto; } }
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name { get { return proto.Name; } }
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
IDescriptor lookup = File.DescriptorPool.LookupSymbol(Proto.InputType, this);
|
||||
if (!(lookup is MessageDescriptor))
|
||||
{
|
||||
throw new DescriptorValidationException(this, "\"" + Proto.InputType + "\" is not a message type.");
|
||||
}
|
||||
inputType = (MessageDescriptor) lookup;
|
||||
|
||||
lookup = File.DescriptorPool.LookupSymbol(Proto.OutputType, this);
|
||||
if (!(lookup is MessageDescriptor))
|
||||
{
|
||||
throw new DescriptorValidationException(this, "\"" + Proto.OutputType + "\" is not a message type.");
|
||||
}
|
||||
outputType = (MessageDescriptor) lookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Reflection access for a oneof, allowing clear and "get case" actions.
|
||||
/// </summary>
|
||||
public sealed class OneofAccessor
|
||||
{
|
||||
private readonly Func<IMessage, int> caseDelegate;
|
||||
private readonly Action<IMessage> clearDelegate;
|
||||
|
||||
private OneofAccessor(OneofDescriptor descriptor, Func<IMessage, int> caseDelegate, Action<IMessage> clearDelegate)
|
||||
{
|
||||
Descriptor = descriptor;
|
||||
this.caseDelegate = caseDelegate;
|
||||
this.clearDelegate = clearDelegate;
|
||||
}
|
||||
|
||||
internal static OneofAccessor ForRegularOneof(
|
||||
OneofDescriptor descriptor,
|
||||
PropertyInfo caseProperty,
|
||||
MethodInfo clearMethod) =>
|
||||
new OneofAccessor(
|
||||
descriptor,
|
||||
ReflectionUtil.CreateFuncIMessageInt32(caseProperty.GetGetMethod()),
|
||||
ReflectionUtil.CreateActionIMessage(clearMethod));
|
||||
|
||||
internal static OneofAccessor ForSyntheticOneof(OneofDescriptor descriptor)
|
||||
{
|
||||
// Note: descriptor.Fields will be null when this method is called, because we haven't
|
||||
// cross-linked yet. But by the time the delegates are called by user code, all will be
|
||||
// well. (That's why we capture the descriptor itself rather than a field.)
|
||||
return new OneofAccessor(descriptor,
|
||||
message => descriptor.Fields[0].Accessor.HasValue(message) ? descriptor.Fields[0].FieldNumber : 0,
|
||||
message => descriptor.Fields[0].Accessor.Clear(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the descriptor for this oneof.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The descriptor of the oneof.
|
||||
/// </value>
|
||||
public OneofDescriptor Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the oneof in the specified message.
|
||||
/// </summary>
|
||||
public void Clear(IMessage message) => clearDelegate(message);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which field in the oneof is set for specified message
|
||||
/// </summary>
|
||||
public FieldDescriptor GetCaseFieldDescriptor(IMessage message)
|
||||
{
|
||||
int fieldNumber = caseDelegate(message);
|
||||
return fieldNumber > 0
|
||||
? Descriptor.ContainingType.FindFieldByNumber(fieldNumber)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a "oneof" field collection in a message type: a set of
|
||||
/// fields of which at most one can be set in any particular message.
|
||||
/// </summary>
|
||||
public sealed class OneofDescriptor : DescriptorBase
|
||||
{
|
||||
private readonly OneofDescriptorProto proto;
|
||||
private MessageDescriptor containingType;
|
||||
private IList<FieldDescriptor> fields;
|
||||
private readonly OneofAccessor accessor;
|
||||
|
||||
internal OneofDescriptor(OneofDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string clrName)
|
||||
: base(file, file.ComputeFullName(parent, proto.Name), index)
|
||||
{
|
||||
this.proto = proto;
|
||||
containingType = parent;
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
|
||||
// It's useful to determine whether or not this is a synthetic oneof before cross-linking. That means
|
||||
// diving into the proto directly rather than using FieldDescriptor, but that's okay.
|
||||
var firstFieldInOneof = parent.Proto.Field.FirstOrDefault(fieldProto => fieldProto.HasOneofIndex && fieldProto.OneofIndex == index);
|
||||
IsSynthetic = firstFieldInOneof?.Proto3Optional ?? false;
|
||||
|
||||
accessor = CreateAccessor(clrName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name { get { return proto.Name; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message type containing this oneof.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message type containing this oneof.
|
||||
/// </value>
|
||||
public MessageDescriptor ContainingType
|
||||
{
|
||||
get { return containingType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fields within this oneof, in declaration order.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The fields within this oneof, in declaration order.
|
||||
/// </value>
|
||||
public IList<FieldDescriptor> Fields { get { return fields; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if this oneof is a synthetic oneof containing a proto3 optional field;
|
||||
/// <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsSynthetic { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an accessor for reflective access to the values associated with the oneof
|
||||
/// in a particular message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In descriptors for generated code, the value returned by this property will always be non-null.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In dynamically loaded descriptors, the value returned by this property will current be null;
|
||||
/// if and when dynamic messages are supported, it will return a suitable accessor to work with
|
||||
/// them.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// The accessor used for reflective access.
|
||||
/// </value>
|
||||
public OneofAccessor Accessor { get { return accessor; } }
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this oneof.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>OneofOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public OneofOptions GetOptions() => proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value oneof option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<OneofOptions, T> extension)
|
||||
{
|
||||
var value = proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value oneof option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<OneofOptions, T> extension)
|
||||
{
|
||||
return proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
|
||||
foreach (var field in ContainingType.Fields.InDeclarationOrder())
|
||||
{
|
||||
if (field.ContainingOneof == this)
|
||||
{
|
||||
fieldCollection.Add(field);
|
||||
}
|
||||
}
|
||||
fields = new ReadOnlyCollection<FieldDescriptor>(fieldCollection);
|
||||
}
|
||||
|
||||
private OneofAccessor CreateAccessor(string clrName)
|
||||
{
|
||||
// We won't have a CLR name if this is from a dynamically-loaded FileDescriptor.
|
||||
// TODO: Support dynamic messages.
|
||||
if (clrName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (IsSynthetic)
|
||||
{
|
||||
return OneofAccessor.ForSyntheticOneof(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var caseProperty = containingType.ClrType.GetProperty(clrName + "Case");
|
||||
if (caseProperty == null)
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"Property {clrName}Case not found in {containingType.ClrType}");
|
||||
}
|
||||
if (!caseProperty.CanRead)
|
||||
{
|
||||
throw new ArgumentException($"Cannot read from property {clrName}Case in {containingType.ClrType}");
|
||||
}
|
||||
var clearMethod = containingType.ClrType.GetMethod("Clear" + clrName);
|
||||
if (clearMethod == null)
|
||||
{
|
||||
throw new DescriptorValidationException(this, $"Method Clear{clrName} not found in {containingType.ClrType}");
|
||||
}
|
||||
return OneofAccessor.ForRegularOneof(this, caseProperty, clearMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the original name (in the .proto file) of a named element,
|
||||
/// such as an enum value.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class OriginalNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the element in the .proto file.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the name is preferred in the .proto file.
|
||||
/// </summary>
|
||||
public bool PreferredAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new attribute instance for the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element in the .proto file.</param>
|
||||
public OriginalNameAttribute(string name)
|
||||
{
|
||||
Name = ProtoPreconditions.CheckNotNull(name, nameof(name));
|
||||
PreferredAlias = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a package in the symbol table. We use PackageDescriptors
|
||||
/// just as placeholders so that someone cannot define, say, a message type
|
||||
/// that has the same name as an existing package.
|
||||
/// </summary>
|
||||
internal sealed class PackageDescriptor : IDescriptor
|
||||
{
|
||||
private readonly string name;
|
||||
private readonly string fullName;
|
||||
private readonly FileDescriptor file;
|
||||
|
||||
internal PackageDescriptor(string name, string fullName, FileDescriptor file)
|
||||
{
|
||||
this.file = file;
|
||||
this.fullName = fullName;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get { return fullName; }
|
||||
}
|
||||
|
||||
public FileDescriptor File
|
||||
{
|
||||
get { return file; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// The methods in this class are somewhat evil, and should not be tampered with lightly.
|
||||
/// Basically they allow the creation of relatively weakly typed delegates from MethodInfos
|
||||
/// which are more strongly typed. They do this by creating an appropriate strongly typed
|
||||
/// delegate from the MethodInfo, and then calling that within an anonymous method.
|
||||
/// Mind-bending stuff (at least to your humble narrator) but the resulting delegates are
|
||||
/// very fast compared with calling Invoke later on.
|
||||
/// </summary>
|
||||
internal static class ReflectionUtil
|
||||
{
|
||||
static ReflectionUtil()
|
||||
{
|
||||
ForceInitialize<string>(); // Handles all reference types
|
||||
ForceInitialize<int>();
|
||||
ForceInitialize<long>();
|
||||
ForceInitialize<uint>();
|
||||
ForceInitialize<ulong>();
|
||||
ForceInitialize<float>();
|
||||
ForceInitialize<double>();
|
||||
ForceInitialize<bool>();
|
||||
ForceInitialize<int?>();
|
||||
ForceInitialize<long?>();
|
||||
ForceInitialize<uint?>();
|
||||
ForceInitialize<ulong?>();
|
||||
ForceInitialize<float?>();
|
||||
ForceInitialize<double?>();
|
||||
ForceInitialize<bool?>();
|
||||
ForceInitialize<SampleEnum>();
|
||||
SampleEnumMethod();
|
||||
}
|
||||
|
||||
internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>();
|
||||
|
||||
/// <summary>
|
||||
/// Empty Type[] used when calling GetProperty to force property instead of indexer fetching.
|
||||
/// </summary>
|
||||
internal static readonly Type[] EmptyTypes = new Type[0];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate which will cast the argument to the type that declares the method,
|
||||
/// call the method on it, then convert the result to object.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to create a delegate for, which must be declared in an IMessage
|
||||
/// implementation.</param>
|
||||
internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) =>
|
||||
GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate which will cast the argument to the type that declares the method,
|
||||
/// call the method on it, then convert the result to the specified type. The method is expected
|
||||
/// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that
|
||||
/// means we need some extra work to perform conversions.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to create a delegate for, which must be declared in an IMessage
|
||||
/// implementation.</param>
|
||||
internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) =>
|
||||
GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate which will execute the given method after casting the first argument to
|
||||
/// the type that declares the method, and the second argument to the first parameter type of the method.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to create a delegate for, which must be declared in an IMessage
|
||||
/// implementation.</param>
|
||||
internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) =>
|
||||
GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate which will execute the given method after casting the first argument to
|
||||
/// type that declares the method.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to create a delegate for, which must be declared in an IMessage
|
||||
/// implementation.</param>
|
||||
internal static Action<IMessage> CreateActionIMessage(MethodInfo method) =>
|
||||
GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method);
|
||||
|
||||
internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) =>
|
||||
GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method);
|
||||
|
||||
internal static Func<IMessage, bool> CreateIsInitializedCaller(Type msg) =>
|
||||
((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate which will execute the given method after casting the first argument to
|
||||
/// the type that declares the method, and the second argument to the first parameter type of the method.
|
||||
/// </summary>
|
||||
internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) =>
|
||||
(IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1]), extension);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reflection helper for the given type arguments. Currently these are created on demand
|
||||
/// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that
|
||||
/// they can be garbage collected. We could cache them by type if that proves to be important, but creating
|
||||
/// an object is pretty cheap.
|
||||
/// </summary>
|
||||
private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) =>
|
||||
(IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2));
|
||||
|
||||
// Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically
|
||||
// knowing the types involved.
|
||||
private interface IReflectionHelper
|
||||
{
|
||||
Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method);
|
||||
Action<IMessage> CreateActionIMessage(MethodInfo method);
|
||||
Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method);
|
||||
Action<IMessage, object> CreateActionIMessageObject(MethodInfo method);
|
||||
Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method);
|
||||
}
|
||||
|
||||
internal interface IExtensionReflectionHelper
|
||||
{
|
||||
object GetExtension(IMessage message);
|
||||
void SetExtension(IMessage message, object value);
|
||||
bool HasExtension(IMessage message);
|
||||
void ClearExtension(IMessage message);
|
||||
}
|
||||
|
||||
private interface IExtensionSetReflector
|
||||
{
|
||||
Func<IMessage, bool> CreateIsInitializedCaller();
|
||||
}
|
||||
|
||||
private class ReflectionHelper<T1, T2> : IReflectionHelper
|
||||
{
|
||||
|
||||
public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method)
|
||||
{
|
||||
// On pleasant runtimes, we can create a Func<int> from a method returning
|
||||
// an enum based on an int. That's the fast path.
|
||||
if (CanConvertEnumFuncToInt32Func)
|
||||
{
|
||||
var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>));
|
||||
return message => del((T1) message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On some runtimes (e.g. old Mono) the return type has to be exactly correct,
|
||||
// so we go via boxing. Reflection is already fairly inefficient, and this is
|
||||
// only used for one-of case checking, fortunately.
|
||||
var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
|
||||
return message => (int) (object) del((T1) message);
|
||||
}
|
||||
}
|
||||
|
||||
public Action<IMessage> CreateActionIMessage(MethodInfo method)
|
||||
{
|
||||
var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>));
|
||||
return message => del((T1) message);
|
||||
}
|
||||
|
||||
public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method)
|
||||
{
|
||||
var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
|
||||
return message => del((T1) message);
|
||||
}
|
||||
|
||||
public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method)
|
||||
{
|
||||
var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>));
|
||||
return (message, arg) => del((T1) message, (T2) arg);
|
||||
}
|
||||
|
||||
public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method)
|
||||
{
|
||||
var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>));
|
||||
return message => del((T1)message);
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper
|
||||
where T1 : IExtendableMessage<T1>
|
||||
{
|
||||
private readonly Extension extension;
|
||||
|
||||
public ExtensionReflectionHelper(Extension extension)
|
||||
{
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public object GetExtension(IMessage message)
|
||||
{
|
||||
if (!(message is T1))
|
||||
{
|
||||
throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
|
||||
}
|
||||
|
||||
T1 extensionMessage = (T1)message;
|
||||
|
||||
if (extension is Extension<T1, T3>)
|
||||
{
|
||||
return extensionMessage.GetExtension(extension as Extension<T1, T3>);
|
||||
}
|
||||
else if (extension is RepeatedExtension<T1, T3>)
|
||||
{
|
||||
return extensionMessage.GetOrInitializeExtension(extension as RepeatedExtension<T1, T3>);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("The provided extension is not a valid extension identifier type");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasExtension(IMessage message)
|
||||
{
|
||||
if (!(message is T1))
|
||||
{
|
||||
throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
|
||||
}
|
||||
|
||||
T1 extensionMessage = (T1)message;
|
||||
|
||||
if (extension is Extension<T1, T3>)
|
||||
{
|
||||
return extensionMessage.HasExtension(extension as Extension<T1, T3>);
|
||||
}
|
||||
else if (extension is RepeatedExtension<T1, T3>)
|
||||
{
|
||||
throw new InvalidOperationException("HasValue is not implemented for repeated extensions");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("The provided extension is not a valid extension identifier type");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetExtension(IMessage message, object value)
|
||||
{
|
||||
if (!(message is T1))
|
||||
{
|
||||
throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
|
||||
}
|
||||
|
||||
T1 extensionMessage = (T1)message;
|
||||
|
||||
if (extension is Extension<T1, T3>)
|
||||
{
|
||||
extensionMessage.SetExtension(extension as Extension<T1, T3>, (T3)value);
|
||||
}
|
||||
else if (extension is RepeatedExtension<T1, T3>)
|
||||
{
|
||||
throw new InvalidOperationException("SetValue is not implemented for repeated extensions");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("The provided extension is not a valid extension identifier type");
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearExtension(IMessage message)
|
||||
{
|
||||
if (!(message is T1))
|
||||
{
|
||||
throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
|
||||
}
|
||||
|
||||
T1 extensionMessage = (T1)message;
|
||||
|
||||
if (extension is Extension<T1, T3>)
|
||||
{
|
||||
extensionMessage.ClearExtension(extension as Extension<T1, T3>);
|
||||
}
|
||||
else if (extension is RepeatedExtension<T1, T3>)
|
||||
{
|
||||
extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>).Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("The provided extension is not a valid extension identifier type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtensionSetReflector<T1> : IExtensionSetReflector where T1 : IExtendableMessage<T1>
|
||||
{
|
||||
public Func<IMessage, bool> CreateIsInitializedCaller()
|
||||
{
|
||||
var prop = typeof(T1).GetTypeInfo().GetDeclaredProperty("_Extensions");
|
||||
#if NET35
|
||||
var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetGetMethod(true).CreateDelegate(typeof(Func<T1, ExtensionSet<T1>>));
|
||||
#else
|
||||
var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetMethod.CreateDelegate(typeof(Func<T1, ExtensionSet<T1>>));
|
||||
#endif
|
||||
var initializedFunc = (Func<ExtensionSet<T1>, bool>)
|
||||
typeof(ExtensionSet<T1>)
|
||||
.GetTypeInfo()
|
||||
.GetDeclaredMethod("IsInitialized")
|
||||
.CreateDelegate(typeof(Func<ExtensionSet<T1>, bool>));
|
||||
return (m) => {
|
||||
var set = getFunc((T1)m);
|
||||
return set == null || initializedFunc(set);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for
|
||||
// details about why we're doing this.
|
||||
|
||||
// Deliberately not inside the generic type. We only want to check this once.
|
||||
private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func();
|
||||
|
||||
private static bool CheckCanConvertEnumFuncToInt32Func()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to do the conversion using reflection, so we can see whether it's supported.
|
||||
MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod));
|
||||
// If this passes, we're in a reasonable runtime.
|
||||
method.CreateDelegate(typeof(Func<int>));
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SampleEnum
|
||||
{
|
||||
X
|
||||
}
|
||||
|
||||
// Public to make the reflection simpler.
|
||||
public static SampleEnum SampleEnumMethod() => SampleEnum.X;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Accessor for repeated fields.
|
||||
/// </summary>
|
||||
internal sealed class RepeatedFieldAccessor : FieldAccessorBase
|
||||
{
|
||||
internal RepeatedFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Clear(IMessage message)
|
||||
{
|
||||
IList list = (IList) GetValue(message);
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
public override bool HasValue(IMessage message)
|
||||
{
|
||||
throw new InvalidOperationException("HasValue is not implemented for repeated fields");
|
||||
}
|
||||
|
||||
public override void SetValue(IMessage message, object value)
|
||||
{
|
||||
throw new InvalidOperationException("SetValue is not implemented for repeated fields");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using LC.Google.Protobuf.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a service type.
|
||||
/// </summary>
|
||||
public sealed class ServiceDescriptor : DescriptorBase
|
||||
{
|
||||
private readonly ServiceDescriptorProto proto;
|
||||
private readonly IList<MethodDescriptor> methods;
|
||||
|
||||
internal ServiceDescriptor(ServiceDescriptorProto proto, FileDescriptor file, int index)
|
||||
: base(file, file.ComputeFullName(null, proto.Name), index)
|
||||
{
|
||||
this.proto = proto;
|
||||
methods = DescriptorUtil.ConvertAndMakeReadOnly(proto.Method,
|
||||
(method, i) => new MethodDescriptor(method, file, this, i));
|
||||
|
||||
file.DescriptorPool.AddSymbol(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The brief name of the descriptor's target.
|
||||
/// </summary>
|
||||
public override string Name { get { return proto.Name; } }
|
||||
|
||||
internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
|
||||
{
|
||||
switch (fieldNumber)
|
||||
{
|
||||
case ServiceDescriptorProto.MethodFieldNumber:
|
||||
return (IReadOnlyList<DescriptorBase>) methods;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal ServiceDescriptorProto Proto { get { return proto; } }
|
||||
|
||||
/// <value>
|
||||
/// An unmodifiable list of methods in this service.
|
||||
/// </value>
|
||||
public IList<MethodDescriptor> Methods
|
||||
{
|
||||
get { return methods; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a method by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The unqualified name of the method (e.g. "Foo").</param>
|
||||
/// <returns>The method's descriptor, or null if not found.</returns>
|
||||
public MethodDescriptor FindMethodByName(String name)
|
||||
{
|
||||
return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The (possibly empty) set of custom options for this service.
|
||||
/// </summary>
|
||||
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
|
||||
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
|
||||
|
||||
/// <summary>
|
||||
/// The <c>ServiceOptions</c>, defined in <c>descriptor.proto</c>.
|
||||
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
|
||||
/// Custom options can be retrieved as extensions of the returned message.
|
||||
/// NOTE: A defensive copy is created each time this property is retrieved.
|
||||
/// </summary>
|
||||
public ServiceOptions GetOptions() => Proto.Options?.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single value service option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public T GetOption<T>(Extension<ServiceOptions, T> extension)
|
||||
{
|
||||
var value = Proto.Options.GetExtension(extension);
|
||||
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a repeated value service option for this descriptor
|
||||
/// </summary>
|
||||
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
|
||||
public RepeatedField<T> GetOption<T>(RepeatedExtension<ServiceOptions, T> extension)
|
||||
{
|
||||
return Proto.Options.GetExtension(extension).Clone();
|
||||
}
|
||||
|
||||
internal void CrossLink()
|
||||
{
|
||||
foreach (MethodDescriptor method in methods)
|
||||
{
|
||||
method.CrossLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using LC.Google.Protobuf.Compatibility;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Accessor for single fields.
|
||||
/// </summary>
|
||||
internal sealed class SingleFieldAccessor : FieldAccessorBase
|
||||
{
|
||||
// All the work here is actually done in the constructor - it creates the appropriate delegates.
|
||||
// There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive)
|
||||
// and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default
|
||||
// values.
|
||||
|
||||
private readonly Action<IMessage, object> setValueDelegate;
|
||||
private readonly Action<IMessage> clearDelegate;
|
||||
private readonly Func<IMessage, bool> hasDelegate;
|
||||
|
||||
internal SingleFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor)
|
||||
{
|
||||
if (!property.CanWrite)
|
||||
{
|
||||
throw new ArgumentException("Not all required properties/methods available");
|
||||
}
|
||||
setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod());
|
||||
|
||||
// Note: this looks worrying in that we access the containing oneof, which isn't valid until cross-linking
|
||||
// is complete... but field accessors aren't created until after cross-linking.
|
||||
// The oneof itself won't be cross-linked yet, but that's okay: the oneof accessor is created
|
||||
// earlier.
|
||||
|
||||
// Message fields always support presence, via null checks.
|
||||
if (descriptor.FieldType == FieldType.Message)
|
||||
{
|
||||
hasDelegate = message => GetValue(message) != null;
|
||||
clearDelegate = message => SetValue(message, null);
|
||||
}
|
||||
// Oneof fields always support presence, via case checks.
|
||||
// Note that clearing the field is a no-op unless that specific field is the current "case".
|
||||
else if (descriptor.RealContainingOneof != null)
|
||||
{
|
||||
var oneofAccessor = descriptor.RealContainingOneof.Accessor;
|
||||
hasDelegate = message => oneofAccessor.GetCaseFieldDescriptor(message) == descriptor;
|
||||
clearDelegate = message =>
|
||||
{
|
||||
// Clear on a field only affects the oneof itself if the current case is the field we're accessing.
|
||||
if (oneofAccessor.GetCaseFieldDescriptor(message) == descriptor)
|
||||
{
|
||||
oneofAccessor.Clear(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
// Primitive fields always support presence in proto2, and support presence in proto3 for optional fields.
|
||||
else if (descriptor.File.Syntax == Syntax.Proto2 || descriptor.Proto.Proto3Optional)
|
||||
{
|
||||
MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod;
|
||||
if (hasMethod == null)
|
||||
{
|
||||
throw new ArgumentException("Not all required properties/methods are available");
|
||||
}
|
||||
hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
|
||||
MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
|
||||
if (clearMethod == null)
|
||||
{
|
||||
throw new ArgumentException("Not all required properties/methods are available");
|
||||
}
|
||||
clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
|
||||
}
|
||||
// What's left?
|
||||
// Primitive proto3 fields without the optional keyword, which aren't in oneofs.
|
||||
else
|
||||
{
|
||||
hasDelegate = message => { throw new InvalidOperationException("Presence is not implemented for this field"); };
|
||||
|
||||
// While presence isn't supported, clearing still is; it's just setting to a default value.
|
||||
var clrType = property.PropertyType;
|
||||
|
||||
object defaultValue =
|
||||
clrType == typeof(string) ? ""
|
||||
: clrType == typeof(ByteString) ? ByteString.Empty
|
||||
: Activator.CreateInstance(clrType);
|
||||
clearDelegate = message => SetValue(message, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Clear(IMessage message) => clearDelegate(message);
|
||||
public override bool HasValue(IMessage message) => hasDelegate(message);
|
||||
public override void SetValue(IMessage message, object value) => setValueDelegate(message, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
#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
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LC.Google.Protobuf.Reflection
|
||||
{
|
||||
/// <summary>
|
||||
/// An immutable registry of types which can be looked up by their full name.
|
||||
/// </summary>
|
||||
public sealed class TypeRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty type registry, containing no types.
|
||||
/// </summary>
|
||||
public static TypeRegistry Empty { get; } = new TypeRegistry(new Dictionary<string, MessageDescriptor>());
|
||||
|
||||
private readonly Dictionary<string, MessageDescriptor> fullNameToMessageMap;
|
||||
|
||||
private TypeRegistry(Dictionary<string, MessageDescriptor> fullNameToMessageMap)
|
||||
{
|
||||
this.fullNameToMessageMap = fullNameToMessageMap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find a message descriptor by its full name.
|
||||
/// </summary>
|
||||
/// <param name="fullName">The full name of the message, which is the dot-separated
|
||||
/// combination of package, containing messages and message name</param>
|
||||
/// <returns>The message descriptor corresponding to <paramref name="fullName"/> or null
|
||||
/// if there is no such message descriptor.</returns>
|
||||
public MessageDescriptor Find(string fullName)
|
||||
{
|
||||
MessageDescriptor ret;
|
||||
// Ignore the return value as ret will end up with the right value either way.
|
||||
fullNameToMessageMap.TryGetValue(fullName, out ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a type registry from the specified set of file descriptors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a convenience overload for <see cref="FromFiles(IEnumerable{FileDescriptor})"/>
|
||||
/// to allow calls such as <c>TypeRegistry.FromFiles(descriptor1, descriptor2)</c>.
|
||||
/// </remarks>
|
||||
/// <param name="fileDescriptors">The set of files to include in the registry. Must not contain null values.</param>
|
||||
/// <returns>A type registry for the given files.</returns>
|
||||
public static TypeRegistry FromFiles(params FileDescriptor[] fileDescriptors)
|
||||
{
|
||||
return FromFiles((IEnumerable<FileDescriptor>) fileDescriptors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a type registry from the specified set of file descriptors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All message types within all the specified files are added to the registry, and
|
||||
/// the dependencies of the specified files are also added, recursively.
|
||||
/// </remarks>
|
||||
/// <param name="fileDescriptors">The set of files to include in the registry. Must not contain null values.</param>
|
||||
/// <returns>A type registry for the given files.</returns>
|
||||
public static TypeRegistry FromFiles(IEnumerable<FileDescriptor> fileDescriptors)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(fileDescriptors, nameof(fileDescriptors));
|
||||
var builder = new Builder();
|
||||
foreach (var file in fileDescriptors)
|
||||
{
|
||||
builder.AddFile(file);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a type registry from the file descriptor parents of the specified set of message descriptors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a convenience overload for <see cref="FromMessages(IEnumerable{MessageDescriptor})"/>
|
||||
/// to allow calls such as <c>TypeRegistry.FromFiles(descriptor1, descriptor2)</c>.
|
||||
/// </remarks>
|
||||
/// <param name="messageDescriptors">The set of message descriptors to use to identify file descriptors to include in the registry.
|
||||
/// Must not contain null values.</param>
|
||||
/// <returns>A type registry for the given files.</returns>
|
||||
public static TypeRegistry FromMessages(params MessageDescriptor[] messageDescriptors)
|
||||
{
|
||||
return FromMessages((IEnumerable<MessageDescriptor>) messageDescriptors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a type registry from the file descriptor parents of the specified set of message descriptors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The specified message descriptors are only used to identify their file descriptors; the returned registry
|
||||
/// contains all the types within the file descriptors which contain the specified message descriptors (and
|
||||
/// the dependencies of those files), not just the specified messages.
|
||||
/// </remarks>
|
||||
/// <param name="messageDescriptors">The set of message descriptors to use to identify file descriptors to include in the registry.
|
||||
/// Must not contain null values.</param>
|
||||
/// <returns>A type registry for the given files.</returns>
|
||||
public static TypeRegistry FromMessages(IEnumerable<MessageDescriptor> messageDescriptors)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(messageDescriptors, nameof(messageDescriptors));
|
||||
return FromFiles(messageDescriptors.Select(md => md.File));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder class which isn't exposed, but acts as a convenient alternative to passing round two dictionaries in recursive calls.
|
||||
/// </summary>
|
||||
private class Builder
|
||||
{
|
||||
private readonly Dictionary<string, MessageDescriptor> types;
|
||||
private readonly HashSet<string> fileDescriptorNames;
|
||||
|
||||
internal Builder()
|
||||
{
|
||||
types = new Dictionary<string, MessageDescriptor>();
|
||||
fileDescriptorNames = new HashSet<string>();
|
||||
}
|
||||
|
||||
internal void AddFile(FileDescriptor fileDescriptor)
|
||||
{
|
||||
if (!fileDescriptorNames.Add(fileDescriptor.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var dependency in fileDescriptor.Dependencies)
|
||||
{
|
||||
AddFile(dependency);
|
||||
}
|
||||
foreach (var message in fileDescriptor.MessageTypes)
|
||||
{
|
||||
AddMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMessage(MessageDescriptor messageDescriptor)
|
||||
{
|
||||
foreach (var nestedType in messageDescriptor.NestedTypes)
|
||||
{
|
||||
AddMessage(nestedType);
|
||||
}
|
||||
// This will overwrite any previous entry. Given that each file should
|
||||
// only be added once, this could be a problem such as package A.B with type C,
|
||||
// and package A with type B.C... it's unclear what we should do in that case.
|
||||
types[messageDescriptor.FullName] = messageDescriptor;
|
||||
}
|
||||
|
||||
internal TypeRegistry Build()
|
||||
{
|
||||
return new TypeRegistry(types);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,296 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for reading from a stream / read only sequence.
|
||||
/// Parsing from the buffer is a loop of reading from current buffer / refreshing the buffer once done.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
internal struct SegmentedBufferHelper
|
||||
{
|
||||
private int? totalLength;
|
||||
private ReadOnlySequence<byte>.Enumerator readOnlySequenceEnumerator;
|
||||
private CodedInputStream codedInputStream;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance with a coded input stream.
|
||||
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
|
||||
/// and we can write directly into it without copying.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Initialize(CodedInputStream codedInputStream, out SegmentedBufferHelper instance)
|
||||
{
|
||||
instance.totalLength = codedInputStream.InternalInputStream == null ? (int?)codedInputStream.InternalBuffer.Length : null;
|
||||
instance.readOnlySequenceEnumerator = default;
|
||||
instance.codedInputStream = codedInputStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance with a read only sequence.
|
||||
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
|
||||
/// and we can write directly into it without copying.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Initialize(ReadOnlySequence<byte> sequence, out SegmentedBufferHelper instance, out ReadOnlySpan<byte> firstSpan)
|
||||
{
|
||||
instance.codedInputStream = null;
|
||||
if (sequence.IsSingleSegment)
|
||||
{
|
||||
firstSpan = sequence.First.Span;
|
||||
instance.totalLength = firstSpan.Length;
|
||||
instance.readOnlySequenceEnumerator = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.readOnlySequenceEnumerator = sequence.GetEnumerator();
|
||||
instance.totalLength = (int) sequence.Length;
|
||||
|
||||
// set firstSpan to the first segment
|
||||
instance.readOnlySequenceEnumerator.MoveNext();
|
||||
firstSpan = instance.readOnlySequenceEnumerator.Current.Span;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RefillBuffer(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
|
||||
{
|
||||
if (codedInputStream != null)
|
||||
{
|
||||
return RefillFromCodedInputStream(ref buffer, ref state, mustSucceed);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RefillFromReadOnlySequence(ref buffer, ref state, mustSucceed);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TotalLength => totalLength;
|
||||
|
||||
public CodedInputStream CodedInputStream => codedInputStream;
|
||||
|
||||
/// <summary>
|
||||
/// Sets currentLimit to (current position) + byteLimit. This is called
|
||||
/// when descending into a length-delimited embedded message. The previous
|
||||
/// limit is returned.
|
||||
/// </summary>
|
||||
/// <returns>The old limit.</returns>
|
||||
public static int PushLimit(ref ParserInternalState state, int byteLimit)
|
||||
{
|
||||
if (byteLimit < 0)
|
||||
{
|
||||
throw InvalidProtocolBufferException.NegativeSize();
|
||||
}
|
||||
byteLimit += state.totalBytesRetired + state.bufferPos;
|
||||
int oldLimit = state.currentLimit;
|
||||
if (byteLimit > oldLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
state.currentLimit = byteLimit;
|
||||
|
||||
RecomputeBufferSizeAfterLimit(ref state);
|
||||
|
||||
return oldLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards the current limit, returning the previous limit.
|
||||
/// </summary>
|
||||
public static void PopLimit(ref ParserInternalState state, int oldLimit)
|
||||
{
|
||||
state.currentLimit = oldLimit;
|
||||
RecomputeBufferSizeAfterLimit(ref state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not all the data before the limit has been read.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsReachedLimit(ref ParserInternalState state)
|
||||
{
|
||||
if (state.currentLimit == int.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int currentAbsolutePosition = state.totalBytesRetired + state.bufferPos;
|
||||
return currentAbsolutePosition >= state.currentLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream has reached the end of the input. This is the
|
||||
/// case if either the end of the underlying input source has been reached or
|
||||
/// the stream has reached a limit created using PushLimit.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAtEnd(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
|
||||
{
|
||||
return state.bufferPos == state.bufferSize && !state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, false);
|
||||
}
|
||||
|
||||
private bool RefillFromReadOnlySequence(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
|
||||
{
|
||||
CheckCurrentBufferIsEmpty(ref state);
|
||||
|
||||
if (state.totalBytesRetired + state.bufferSize == state.currentLimit)
|
||||
{
|
||||
// Oops, we hit a limit.
|
||||
if (mustSucceed)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
state.totalBytesRetired += state.bufferSize;
|
||||
|
||||
state.bufferPos = 0;
|
||||
state.bufferSize = 0;
|
||||
while (readOnlySequenceEnumerator.MoveNext())
|
||||
{
|
||||
buffer = readOnlySequenceEnumerator.Current.Span;
|
||||
state.bufferSize = buffer.Length;
|
||||
if (buffer.Length != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.bufferSize == 0)
|
||||
{
|
||||
if (mustSucceed)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecomputeBufferSizeAfterLimit(ref state);
|
||||
int totalBytesRead =
|
||||
state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit;
|
||||
if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.SizeLimitExceeded();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool RefillFromCodedInputStream(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
|
||||
{
|
||||
CheckCurrentBufferIsEmpty(ref state);
|
||||
|
||||
if (state.totalBytesRetired + state.bufferSize == state.currentLimit)
|
||||
{
|
||||
// Oops, we hit a limit.
|
||||
if (mustSucceed)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Stream input = codedInputStream.InternalInputStream;
|
||||
|
||||
state.totalBytesRetired += state.bufferSize;
|
||||
|
||||
state.bufferPos = 0;
|
||||
state.bufferSize = (input == null) ? 0 : input.Read(codedInputStream.InternalBuffer, 0, buffer.Length);
|
||||
if (state.bufferSize < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Stream.Read returned a negative count");
|
||||
}
|
||||
if (state.bufferSize == 0)
|
||||
{
|
||||
if (mustSucceed)
|
||||
{
|
||||
throw InvalidProtocolBufferException.TruncatedMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecomputeBufferSizeAfterLimit(ref state);
|
||||
int totalBytesRead =
|
||||
state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit;
|
||||
if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit)
|
||||
{
|
||||
throw InvalidProtocolBufferException.SizeLimitExceeded();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RecomputeBufferSizeAfterLimit(ref ParserInternalState state)
|
||||
{
|
||||
state.bufferSize += state.bufferSizeAfterLimit;
|
||||
int bufferEnd = state.totalBytesRetired + state.bufferSize;
|
||||
if (bufferEnd > state.currentLimit)
|
||||
{
|
||||
// Limit is in current buffer.
|
||||
state.bufferSizeAfterLimit = bufferEnd - state.currentLimit;
|
||||
state.bufferSize -= state.bufferSizeAfterLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.bufferSizeAfterLimit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckCurrentBufferIsEmpty(ref ParserInternalState state)
|
||||
{
|
||||
if (state.bufferPos < state.bufferSize)
|
||||
{
|
||||
throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2017 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single field in an UnknownFieldSet.
|
||||
///
|
||||
/// An UnknownField consists of four lists of values. The lists correspond
|
||||
/// to the four "wire types" used in the protocol buffer binary format.
|
||||
/// Normally, only one of the four lists will contain any values, since it
|
||||
/// is impossible to define a valid message type that declares two different
|
||||
/// types for the same field number. However, the code is designed to allow
|
||||
/// for the case where the same unknown field number is encountered using
|
||||
/// multiple different wire types.
|
||||
///
|
||||
/// </summary>
|
||||
internal sealed class UnknownField
|
||||
{
|
||||
private List<ulong> varintList;
|
||||
private List<uint> fixed32List;
|
||||
private List<ulong> fixed64List;
|
||||
private List<ByteString> lengthDelimitedList;
|
||||
private List<UnknownFieldSet> groupList;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new UnknownField.
|
||||
/// </summary>
|
||||
public UnknownField()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two unknown field are equal.
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
UnknownField otherField = other as UnknownField;
|
||||
return otherField != null
|
||||
&& Lists.Equals(varintList, otherField.varintList)
|
||||
&& Lists.Equals(fixed32List, otherField.fixed32List)
|
||||
&& Lists.Equals(fixed64List, otherField.fixed64List)
|
||||
&& Lists.Equals(lengthDelimitedList, otherField.lengthDelimitedList)
|
||||
&& Lists.Equals(groupList, otherField.groupList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hash code of the unknown field.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 43;
|
||||
hash = hash * 47 + Lists.GetHashCode(varintList);
|
||||
hash = hash * 47 + Lists.GetHashCode(fixed32List);
|
||||
hash = hash * 47 + Lists.GetHashCode(fixed64List);
|
||||
hash = hash * 47 + Lists.GetHashCode(lengthDelimitedList);
|
||||
hash = hash * 47 + Lists.GetHashCode(groupList);
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the field, including the field number, and writes it to
|
||||
/// <paramref name="output"/>
|
||||
/// </summary>
|
||||
/// <param name="fieldNumber">The unknown field number.</param>
|
||||
/// <param name="output">The write context to write to.</param>
|
||||
internal void WriteTo(int fieldNumber, ref WriteContext output)
|
||||
{
|
||||
if (varintList != null)
|
||||
{
|
||||
foreach (ulong value in varintList)
|
||||
{
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.Varint);
|
||||
output.WriteUInt64(value);
|
||||
}
|
||||
}
|
||||
if (fixed32List != null)
|
||||
{
|
||||
foreach (uint value in fixed32List)
|
||||
{
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.Fixed32);
|
||||
output.WriteFixed32(value);
|
||||
}
|
||||
}
|
||||
if (fixed64List != null)
|
||||
{
|
||||
foreach (ulong value in fixed64List)
|
||||
{
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.Fixed64);
|
||||
output.WriteFixed64(value);
|
||||
}
|
||||
}
|
||||
if (lengthDelimitedList != null)
|
||||
{
|
||||
foreach (ByteString value in lengthDelimitedList)
|
||||
{
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited);
|
||||
output.WriteBytes(value);
|
||||
}
|
||||
}
|
||||
if (groupList != null)
|
||||
{
|
||||
foreach (UnknownFieldSet value in groupList)
|
||||
{
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.StartGroup);
|
||||
value.WriteTo(ref output);
|
||||
output.WriteTag(fieldNumber, WireFormat.WireType.EndGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of bytes required to encode this field, including field
|
||||
/// number.
|
||||
/// </summary>
|
||||
internal int GetSerializedSize(int fieldNumber)
|
||||
{
|
||||
int result = 0;
|
||||
if (varintList != null)
|
||||
{
|
||||
result += CodedOutputStream.ComputeTagSize(fieldNumber) * varintList.Count;
|
||||
foreach (ulong value in varintList)
|
||||
{
|
||||
result += CodedOutputStream.ComputeUInt64Size(value);
|
||||
}
|
||||
}
|
||||
if (fixed32List != null)
|
||||
{
|
||||
result += CodedOutputStream.ComputeTagSize(fieldNumber) * fixed32List.Count;
|
||||
result += CodedOutputStream.ComputeFixed32Size(1) * fixed32List.Count;
|
||||
}
|
||||
if (fixed64List != null)
|
||||
{
|
||||
result += CodedOutputStream.ComputeTagSize(fieldNumber) * fixed64List.Count;
|
||||
result += CodedOutputStream.ComputeFixed64Size(1) * fixed64List.Count;
|
||||
}
|
||||
if (lengthDelimitedList != null)
|
||||
{
|
||||
result += CodedOutputStream.ComputeTagSize(fieldNumber) * lengthDelimitedList.Count;
|
||||
foreach (ByteString value in lengthDelimitedList)
|
||||
{
|
||||
result += CodedOutputStream.ComputeBytesSize(value);
|
||||
}
|
||||
}
|
||||
if (groupList != null)
|
||||
{
|
||||
result += CodedOutputStream.ComputeTagSize(fieldNumber) * 2 * groupList.Count;
|
||||
foreach (UnknownFieldSet value in groupList)
|
||||
{
|
||||
result += value.CalculateSize();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge the values in <paramref name="other" /> into this field. For each list
|
||||
/// of values, <paramref name="other"/>'s values are append to the ones in this
|
||||
/// field.
|
||||
/// </summary>
|
||||
internal UnknownField MergeFrom(UnknownField other)
|
||||
{
|
||||
varintList = AddAll(varintList, other.varintList);
|
||||
fixed32List = AddAll(fixed32List, other.fixed32List);
|
||||
fixed64List = AddAll(fixed64List, other.fixed64List);
|
||||
lengthDelimitedList = AddAll(lengthDelimitedList, other.lengthDelimitedList);
|
||||
groupList = AddAll(groupList, other.groupList);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new list containing all of the given specified values from
|
||||
/// both the <paramref name="current"/> and <paramref name="extras"/> lists.
|
||||
/// If <paramref name="current" /> is null and <paramref name="extras"/> is null or empty,
|
||||
/// null is returned. Otherwise, either a new list is created (if <paramref name="current" />
|
||||
/// is null) or the elements of <paramref name="extras"/> are added to <paramref name="current" />.
|
||||
/// </summary>
|
||||
private static List<T> AddAll<T>(List<T> current, IList<T> extras)
|
||||
{
|
||||
if (extras == null || extras.Count == 0)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
if (current == null)
|
||||
{
|
||||
current = new List<T>(extras);
|
||||
}
|
||||
else
|
||||
{
|
||||
current.AddRange(extras);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a varint value.
|
||||
/// </summary>
|
||||
internal UnknownField AddVarint(ulong value)
|
||||
{
|
||||
varintList = Add(varintList, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a fixed32 value.
|
||||
/// </summary>
|
||||
internal UnknownField AddFixed32(uint value)
|
||||
{
|
||||
fixed32List = Add(fixed32List, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a fixed64 value.
|
||||
/// </summary>
|
||||
internal UnknownField AddFixed64(ulong value)
|
||||
{
|
||||
fixed64List = Add(fixed64List, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a length-delimited value.
|
||||
/// </summary>
|
||||
internal UnknownField AddLengthDelimited(ByteString value)
|
||||
{
|
||||
lengthDelimitedList = Add(lengthDelimitedList, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal UnknownField AddGroup(UnknownFieldSet value)
|
||||
{
|
||||
groupList = Add(groupList, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds <paramref name="value"/> to the <paramref name="list"/>, creating
|
||||
/// a new list if <paramref name="list"/> is null. The list is returned - either
|
||||
/// the original reference or the new list.
|
||||
/// </summary>
|
||||
private static List<T> Add<T>(List<T> list, T value)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<T>();
|
||||
}
|
||||
list.Add(value);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to keep track of fields which were seen when parsing a protocol message
|
||||
/// but whose field numbers or types are unrecognized. This most frequently
|
||||
/// occurs when new fields are added to a message type and then messages containing
|
||||
/// those fields are read by old software that was built before the new types were
|
||||
/// added.
|
||||
///
|
||||
/// Most users will never need to use this class directly.
|
||||
/// </summary>
|
||||
public sealed partial class UnknownFieldSet
|
||||
{
|
||||
private readonly IDictionary<int, UnknownField> fields;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new UnknownFieldSet.
|
||||
/// </summary>
|
||||
internal UnknownFieldSet()
|
||||
{
|
||||
this.fields = new Dictionary<int, UnknownField>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not the given field number is present in the set.
|
||||
/// </summary>
|
||||
internal bool HasField(int field)
|
||||
{
|
||||
return fields.ContainsKey(field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the set and writes it to <paramref name="output"/>.
|
||||
/// </summary>
|
||||
public void WriteTo(CodedOutputStream output)
|
||||
{
|
||||
WriteContext.Initialize(output, out WriteContext ctx);
|
||||
try
|
||||
{
|
||||
WriteTo(ref ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the set and writes it to <paramref name="ctx"/>.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public void WriteTo(ref WriteContext ctx)
|
||||
{
|
||||
foreach (KeyValuePair<int, UnknownField> entry in fields)
|
||||
{
|
||||
entry.Value.WriteTo(entry.Key, ref ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes required to encode this set.
|
||||
/// </summary>
|
||||
public int CalculateSize()
|
||||
{
|
||||
int result = 0;
|
||||
foreach (KeyValuePair<int, UnknownField> entry in fields)
|
||||
{
|
||||
result += entry.Value.GetSerializedSize(entry.Key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two unknown field sets are equal.
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
UnknownFieldSet otherSet = other as UnknownFieldSet;
|
||||
IDictionary<int, UnknownField> otherFields = otherSet.fields;
|
||||
if (fields.Count != otherFields.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (KeyValuePair<int, UnknownField> leftEntry in fields)
|
||||
{
|
||||
UnknownField rightValue;
|
||||
if (!otherFields.TryGetValue(leftEntry.Key, out rightValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!leftEntry.Value.Equals(rightValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unknown field set's hash code.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int ret = 1;
|
||||
foreach (KeyValuePair<int, UnknownField> field in fields)
|
||||
{
|
||||
// Use ^ here to make the field order irrelevant.
|
||||
int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode();
|
||||
ret ^= hash;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Optimization: We keep around the last field that was
|
||||
// modified so that we can efficiently add to it multiple times in a
|
||||
// row (important when parsing an unknown repeated field).
|
||||
private int lastFieldNumber;
|
||||
private UnknownField lastField;
|
||||
|
||||
private UnknownField GetOrAddField(int number)
|
||||
{
|
||||
if (lastField != null && number == lastFieldNumber)
|
||||
{
|
||||
return lastField;
|
||||
}
|
||||
if (number == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
UnknownField existing;
|
||||
if (fields.TryGetValue(number, out existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
lastField = new UnknownField();
|
||||
AddOrReplaceField(number, lastField);
|
||||
lastFieldNumber = number;
|
||||
return lastField;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a field to the set. If a field with the same number already exists, it
|
||||
/// is replaced.
|
||||
/// </summary>
|
||||
internal UnknownFieldSet AddOrReplaceField(int number, UnknownField field)
|
||||
{
|
||||
if (number == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
|
||||
}
|
||||
fields[number] = field;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a single field from <paramref name="ctx"/> and merge it
|
||||
/// into this set.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The parse context from which to read the field</param>
|
||||
/// <returns>false if the tag is an "end group" tag, true otherwise</returns>
|
||||
private bool MergeFieldFrom(ref ParseContext ctx)
|
||||
{
|
||||
uint tag = ctx.LastTag;
|
||||
int number = WireFormat.GetTagFieldNumber(tag);
|
||||
switch (WireFormat.GetTagWireType(tag))
|
||||
{
|
||||
case WireFormat.WireType.Varint:
|
||||
{
|
||||
ulong uint64 = ctx.ReadUInt64();
|
||||
GetOrAddField(number).AddVarint(uint64);
|
||||
return true;
|
||||
}
|
||||
case WireFormat.WireType.Fixed32:
|
||||
{
|
||||
uint uint32 = ctx.ReadFixed32();
|
||||
GetOrAddField(number).AddFixed32(uint32);
|
||||
return true;
|
||||
}
|
||||
case WireFormat.WireType.Fixed64:
|
||||
{
|
||||
ulong uint64 = ctx.ReadFixed64();
|
||||
GetOrAddField(number).AddFixed64(uint64);
|
||||
return true;
|
||||
}
|
||||
case WireFormat.WireType.LengthDelimited:
|
||||
{
|
||||
ByteString bytes = ctx.ReadBytes();
|
||||
GetOrAddField(number).AddLengthDelimited(bytes);
|
||||
return true;
|
||||
}
|
||||
case WireFormat.WireType.StartGroup:
|
||||
{
|
||||
UnknownFieldSet set = new UnknownFieldSet();
|
||||
ParsingPrimitivesMessages.ReadGroup(ref ctx, number, set);
|
||||
GetOrAddField(number).AddGroup(set);
|
||||
return true;
|
||||
}
|
||||
case WireFormat.WireType.EndGroup:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw InvalidProtocolBufferException.InvalidWireType();
|
||||
}
|
||||
}
|
||||
|
||||
internal void MergeGroupFrom(ref ParseContext ctx)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
uint tag = ctx.ReadTag();
|
||||
if (tag == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!MergeFieldFrom(ref ctx))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new UnknownFieldSet if unknownFields is null.
|
||||
/// Parse a single field from <paramref name="input"/> and merge it
|
||||
/// into unknownFields. If <paramref name="input"/> is configured to discard unknown fields,
|
||||
/// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped.
|
||||
/// </summary>
|
||||
/// <param name="unknownFields">The UnknownFieldSet which need to be merged</param>
|
||||
/// <param name="input">The coded input stream containing the field</param>
|
||||
/// <returns>The merged UnknownFieldSet</returns>
|
||||
public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
|
||||
CodedInputStream input)
|
||||
{
|
||||
ParseContext.Initialize(input, out ParseContext ctx);
|
||||
try
|
||||
{
|
||||
return MergeFieldFrom(unknownFields, ref ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.CopyStateTo(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new UnknownFieldSet if unknownFields is null.
|
||||
/// Parse a single field from <paramref name="ctx"/> and merge it
|
||||
/// into unknownFields. If <paramref name="ctx"/> is configured to discard unknown fields,
|
||||
/// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped.
|
||||
/// </summary>
|
||||
/// <param name="unknownFields">The UnknownFieldSet which need to be merged</param>
|
||||
/// <param name="ctx">The parse context from which to read the field</param>
|
||||
/// <returns>The merged UnknownFieldSet</returns>
|
||||
[SecuritySafeCritical]
|
||||
public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
|
||||
ref ParseContext ctx)
|
||||
{
|
||||
if (ctx.DiscardUnknownFields)
|
||||
{
|
||||
ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
|
||||
return unknownFields;
|
||||
}
|
||||
if (unknownFields == null)
|
||||
{
|
||||
unknownFields = new UnknownFieldSet();
|
||||
}
|
||||
if (!unknownFields.MergeFieldFrom(ref ctx))
|
||||
{
|
||||
throw new InvalidProtocolBufferException("Merge an unknown field of end-group tag, indicating that the corresponding start-group was missing."); // match the old code-gen
|
||||
}
|
||||
return unknownFields;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the fields from <paramref name="other"/> into this set.
|
||||
/// If a field number exists in both sets, the values in <paramref name="other"/>
|
||||
/// will be appended to the values in this set.
|
||||
/// </summary>
|
||||
private UnknownFieldSet MergeFrom(UnknownFieldSet other)
|
||||
{
|
||||
if (other != null)
|
||||
{
|
||||
foreach (KeyValuePair<int, UnknownField> entry in other.fields)
|
||||
{
|
||||
MergeField(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Created a new UnknownFieldSet to <paramref name="unknownFields"/> if
|
||||
/// needed and merges the fields from <paramref name="other"/> into the first set.
|
||||
/// If a field number exists in both sets, the values in <paramref name="other"/>
|
||||
/// will be appended to the values in this set.
|
||||
/// </summary>
|
||||
public static UnknownFieldSet MergeFrom(UnknownFieldSet unknownFields,
|
||||
UnknownFieldSet other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return unknownFields;
|
||||
}
|
||||
if (unknownFields == null)
|
||||
{
|
||||
unknownFields = new UnknownFieldSet();
|
||||
}
|
||||
unknownFields.MergeFrom(other);
|
||||
return unknownFields;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a field to the unknown field set. If a field with the same
|
||||
/// number already exists, the two are merged.
|
||||
/// </summary>
|
||||
private UnknownFieldSet MergeField(int number, UnknownField field)
|
||||
{
|
||||
if (number == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
|
||||
}
|
||||
if (HasField(number))
|
||||
{
|
||||
GetOrAddField(number).MergeFrom(field);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOrReplaceField(number, field);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone an unknown field set from <paramref name="other"/>.
|
||||
/// </summary>
|
||||
public static UnknownFieldSet Clone(UnknownFieldSet other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
UnknownFieldSet unknownFields = new UnknownFieldSet();
|
||||
unknownFields.MergeFrom(other);
|
||||
return unknownFields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a number of unsafe byte operations to be used by advanced applications with high performance
|
||||
/// requirements. These methods are referred to as "unsafe" due to the fact that they potentially expose
|
||||
/// the backing buffer of a <see cref="ByteString"/> to the application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The methods in this class should only be called if it is guaranteed that the buffer backing the
|
||||
/// <see cref="ByteString"/> will never change! Mutation of a <see cref="ByteString"/> can lead to unexpected
|
||||
/// and undesirable consequences in your application, and will likely be difficult to debug. Proceed with caution!
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This can have a number of significant side affects that have spooky-action-at-a-distance-like behavior. In
|
||||
/// particular, if the bytes value changes out from under a Protocol Buffer:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>serialization may throw</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>serialization may succeed but the wrong bytes may be written out</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>objects that are normally immutable (such as ByteString) are no longer immutable</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>hashCode may be incorrect</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
public static class UnsafeByteOperations
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="ByteString" /> from the given bytes. The bytes are not copied,
|
||||
/// and must not be modified while the <see cref="ByteString" /> is in use.
|
||||
/// This API is experimental and subject to change.
|
||||
/// </summary>
|
||||
public static ByteString UnsafeWrap(ReadOnlyMemory<byte> bytes)
|
||||
{
|
||||
return ByteString.AttachBytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/any.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/any.proto</summary>
|
||||
public static partial class AnyReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/any.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static AnyReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Chlnb29nbGUvcHJvdG9idWYvYW55LnByb3RvEg9nb29nbGUucHJvdG9idWYi",
|
||||
"JgoDQW55EhAKCHR5cGVfdXJsGAEgASgJEg0KBXZhbHVlGAIgASgMQnYKE2Nv",
|
||||
"bS5nb29nbGUucHJvdG9idWZCCEFueVByb3RvUAFaLGdvb2dsZS5nb2xhbmcu",
|
||||
"b3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL2FueXBiogIDR1BCqgIeR29vZ2xl",
|
||||
"LlByb3RvYnVmLldlbGxLbm93blR5cGVzYgZwcm90bzM="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Any), global::LC.Google.Protobuf.WellKnownTypes.Any.Parser, new[]{ "TypeUrl", "Value" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// `Any` contains an arbitrary serialized protocol buffer message along with a
|
||||
/// URL that describes the type of the serialized message.
|
||||
///
|
||||
/// Protobuf library provides support to pack/unpack Any values in the form
|
||||
/// of utility functions or additional generated methods of the Any type.
|
||||
///
|
||||
/// Example 1: Pack and unpack a message in C++.
|
||||
///
|
||||
/// Foo foo = ...;
|
||||
/// Any any;
|
||||
/// any.PackFrom(foo);
|
||||
/// ...
|
||||
/// if (any.UnpackTo(&foo)) {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// Example 2: Pack and unpack a message in Java.
|
||||
///
|
||||
/// Foo foo = ...;
|
||||
/// Any any = Any.pack(foo);
|
||||
/// ...
|
||||
/// if (any.is(Foo.class)) {
|
||||
/// foo = any.unpack(Foo.class);
|
||||
/// }
|
||||
///
|
||||
/// Example 3: Pack and unpack a message in Python.
|
||||
///
|
||||
/// foo = Foo(...)
|
||||
/// any = Any()
|
||||
/// any.Pack(foo)
|
||||
/// ...
|
||||
/// if any.Is(Foo.DESCRIPTOR):
|
||||
/// any.Unpack(foo)
|
||||
/// ...
|
||||
///
|
||||
/// Example 4: Pack and unpack a message in Go
|
||||
///
|
||||
/// foo := &pb.Foo{...}
|
||||
/// any, err := anypb.New(foo)
|
||||
/// if err != nil {
|
||||
/// ...
|
||||
/// }
|
||||
/// ...
|
||||
/// foo := &pb.Foo{}
|
||||
/// if err := any.UnmarshalTo(foo); err != nil {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// The pack methods provided by protobuf library will by default use
|
||||
/// 'type.googleapis.com/full.type.name' as the type URL and the unpack
|
||||
/// methods only use the fully qualified type name after the last '/'
|
||||
/// in the type URL, for example "foo.bar.com/x/y.z" will yield type
|
||||
/// name "y.z".
|
||||
///
|
||||
/// JSON
|
||||
/// ====
|
||||
/// The JSON representation of an `Any` value uses the regular
|
||||
/// representation of the deserialized, embedded message, with an
|
||||
/// additional field `@type` which contains the type URL. Example:
|
||||
///
|
||||
/// package google.profile;
|
||||
/// message Person {
|
||||
/// string first_name = 1;
|
||||
/// string last_name = 2;
|
||||
/// }
|
||||
///
|
||||
/// {
|
||||
/// "@type": "type.googleapis.com/google.profile.Person",
|
||||
/// "firstName": <string>,
|
||||
/// "lastName": <string>
|
||||
/// }
|
||||
///
|
||||
/// If the embedded message type is well-known and has a custom JSON
|
||||
/// representation, that representation will be embedded adding a field
|
||||
/// `value` which holds the custom JSON in addition to the `@type`
|
||||
/// field. Example (for message [google.protobuf.Duration][]):
|
||||
///
|
||||
/// {
|
||||
/// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
/// "value": "1.212s"
|
||||
/// }
|
||||
/// </summary>
|
||||
public sealed partial class Any : pb::IMessage<Any>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Any> _parser = new pb::MessageParser<Any>(() => new Any());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Any> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.AnyReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Any() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Any(Any other) : this() {
|
||||
typeUrl_ = other.typeUrl_;
|
||||
value_ = other.value_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Any Clone() {
|
||||
return new Any(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "type_url" field.</summary>
|
||||
public const int TypeUrlFieldNumber = 1;
|
||||
private string typeUrl_ = "";
|
||||
/// <summary>
|
||||
/// A URL/resource name that uniquely identifies the type of the serialized
|
||||
/// protocol buffer message. This string must contain at least
|
||||
/// one "/" character. The last segment of the URL's path must represent
|
||||
/// the fully qualified name of the type (as in
|
||||
/// `path/google.protobuf.Duration`). The name should be in a canonical form
|
||||
/// (e.g., leading "." is not accepted).
|
||||
///
|
||||
/// In practice, teams usually precompile into the binary all types that they
|
||||
/// expect it to use in the context of Any. However, for URLs which use the
|
||||
/// scheme `http`, `https`, or no scheme, one can optionally set up a type
|
||||
/// server that maps type URLs to message definitions as follows:
|
||||
///
|
||||
/// * If no scheme is provided, `https` is assumed.
|
||||
/// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
/// value in binary format, or produce an error.
|
||||
/// * Applications are allowed to cache lookup results based on the
|
||||
/// URL, or have them precompiled into a binary to avoid any
|
||||
/// lookup. Therefore, binary compatibility needs to be preserved
|
||||
/// on changes to types. (Use versioned type names to manage
|
||||
/// breaking changes.)
|
||||
///
|
||||
/// Note: this functionality is not currently available in the official
|
||||
/// protobuf release, and it is not used for type URLs beginning with
|
||||
/// type.googleapis.com.
|
||||
///
|
||||
/// Schemes other than `http`, `https` (or the empty scheme) might be
|
||||
/// used with implementation specific semantics.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public string TypeUrl {
|
||||
get { return typeUrl_; }
|
||||
set {
|
||||
typeUrl_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "value" field.</summary>
|
||||
public const int ValueFieldNumber = 2;
|
||||
private pb::ByteString value_ = pb::ByteString.Empty;
|
||||
/// <summary>
|
||||
/// Must be a valid serialized protocol buffer of the above specified type.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public pb::ByteString Value {
|
||||
get { return value_; }
|
||||
set {
|
||||
value_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Any);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Any other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (TypeUrl != other.TypeUrl) return false;
|
||||
if (Value != other.Value) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (TypeUrl.Length != 0) hash ^= TypeUrl.GetHashCode();
|
||||
if (Value.Length != 0) hash ^= Value.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (TypeUrl.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(TypeUrl);
|
||||
}
|
||||
if (Value.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteBytes(Value);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (TypeUrl.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(TypeUrl);
|
||||
}
|
||||
if (Value.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteBytes(Value);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (TypeUrl.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(TypeUrl);
|
||||
}
|
||||
if (Value.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeBytesSize(Value);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Any other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.TypeUrl.Length != 0) {
|
||||
TypeUrl = other.TypeUrl;
|
||||
}
|
||||
if (other.Value.Length != 0) {
|
||||
Value = other.Value;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
TypeUrl = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
Value = input.ReadBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
TypeUrl = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
Value = input.ReadBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,147 @@
|
|||
#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
|
||||
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
public partial class Any
|
||||
{
|
||||
private const string DefaultPrefix = "type.googleapis.com";
|
||||
|
||||
// This could be moved to MessageDescriptor if we wanted to, but keeping it here means
|
||||
// all the Any-specific code is in the same place.
|
||||
private static string GetTypeUrl(MessageDescriptor descriptor, string prefix) =>
|
||||
prefix.EndsWith("/") ? prefix + descriptor.FullName : prefix + "/" + descriptor.FullName;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the type name for a type URL, matching the <see cref="DescriptorBase.FullName"/>
|
||||
/// of the packed message type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is always just the last part of the URL, after the final slash. No validation of
|
||||
/// anything before the trailing slash is performed. If the type URL does not include a slash,
|
||||
/// an empty string is returned rather than an exception being thrown; this won't match any types,
|
||||
/// and the calling code is probably in a better position to give a meaningful error.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// There is no handling of fragments or queries at the moment.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="typeUrl">The URL to extract the type name from</param>
|
||||
/// <returns>The type name</returns>
|
||||
public static string GetTypeName(string typeUrl)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(typeUrl, nameof(typeUrl));
|
||||
int lastSlash = typeUrl.LastIndexOf('/');
|
||||
return lastSlash == -1 ? "" : typeUrl.Substring(lastSlash + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a bool indictating whether this Any message is of the target message type
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The descriptor of the message type</param>
|
||||
/// <returns><c>true</c> if the type name matches the descriptor's full name or <c>false</c> otherwise</returns>
|
||||
public bool Is(MessageDescriptor descriptor)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));
|
||||
return GetTypeName(TypeUrl) == descriptor.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the content of this Any message into the target message type,
|
||||
/// which must match the type URL within this Any message.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of message to unpack the content into.</typeparam>
|
||||
/// <returns>The unpacked message.</returns>
|
||||
/// <exception cref="InvalidProtocolBufferException">The target message type doesn't match the type URL in this message</exception>
|
||||
public T Unpack<T>() where T : IMessage, new()
|
||||
{
|
||||
// Note: this doesn't perform as well is it might. We could take a MessageParser<T> in an alternative overload,
|
||||
// which would be expected to perform slightly better... although the difference is likely to be negligible.
|
||||
T target = new T();
|
||||
if (GetTypeName(TypeUrl) != target.Descriptor.FullName)
|
||||
{
|
||||
throw new InvalidProtocolBufferException(
|
||||
$"Full type name for {target.Descriptor.Name} is {target.Descriptor.FullName}; Any message's type url is {TypeUrl}");
|
||||
}
|
||||
target.MergeFrom(Value);
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to unpack the content of this Any message into the target message type,
|
||||
/// if it matches the type URL within this Any message.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of message to attempt to unpack the content into.</typeparam>
|
||||
/// <returns><c>true</c> if the message was successfully unpacked; <c>false</c> if the type name didn't match</returns>
|
||||
public bool TryUnpack<T>(out T result) where T : IMessage, new()
|
||||
{
|
||||
// Note: deliberately avoid writing anything to result until the end, in case it's being
|
||||
// monitored by other threads. (That would be a bug in the calling code, but let's not make it worse.)
|
||||
T target = new T();
|
||||
if (GetTypeName(TypeUrl) != target.Descriptor.FullName)
|
||||
{
|
||||
result = default(T); // Can't use null as there's no class constraint, but this always *will* be null in real usage.
|
||||
return false;
|
||||
}
|
||||
target.MergeFrom(Value);
|
||||
result = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs the specified message into an Any message using a type URL prefix of "type.googleapis.com".
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pack.</param>
|
||||
/// <returns>An Any message with the content and type URL of <paramref name="message"/>.</returns>
|
||||
public static Any Pack(IMessage message) => Pack(message, DefaultPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// Packs the specified message into an Any message using the specified type URL prefix.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to pack.</param>
|
||||
/// <param name="typeUrlPrefix">The prefix for the type URL.</param>
|
||||
/// <returns>An Any message with the content and type URL of <paramref name="message"/>.</returns>
|
||||
public static Any Pack(IMessage message, string typeUrlPrefix)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(message, nameof(message));
|
||||
ProtoPreconditions.CheckNotNull(typeUrlPrefix, nameof(typeUrlPrefix));
|
||||
return new Any
|
||||
{
|
||||
TypeUrl = GetTypeUrl(message.Descriptor, typeUrlPrefix),
|
||||
Value = message.ToByteString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,328 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/duration.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/duration.proto</summary>
|
||||
public static partial class DurationReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/duration.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static DurationReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Ch5nb29nbGUvcHJvdG9idWYvZHVyYXRpb24ucHJvdG8SD2dvb2dsZS5wcm90",
|
||||
"b2J1ZiIqCghEdXJhdGlvbhIPCgdzZWNvbmRzGAEgASgDEg0KBW5hbm9zGAIg",
|
||||
"ASgFQoMBChNjb20uZ29vZ2xlLnByb3RvYnVmQg1EdXJhdGlvblByb3RvUAFa",
|
||||
"MWdvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL2R1cmF0",
|
||||
"aW9ucGL4AQGiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlw",
|
||||
"ZXNiBnByb3RvMw=="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Duration), global::LC.Google.Protobuf.WellKnownTypes.Duration.Parser, new[]{ "Seconds", "Nanos" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// A Duration represents a signed, fixed-length span of time represented
|
||||
/// as a count of seconds and fractions of seconds at nanosecond
|
||||
/// resolution. It is independent of any calendar and concepts like "day"
|
||||
/// or "month". It is related to Timestamp in that the difference between
|
||||
/// two Timestamp values is a Duration and it can be added or subtracted
|
||||
/// from a Timestamp. Range is approximately +-10,000 years.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Example 1: Compute Duration from two Timestamps in pseudo code.
|
||||
///
|
||||
/// Timestamp start = ...;
|
||||
/// Timestamp end = ...;
|
||||
/// Duration duration = ...;
|
||||
///
|
||||
/// duration.seconds = end.seconds - start.seconds;
|
||||
/// duration.nanos = end.nanos - start.nanos;
|
||||
///
|
||||
/// if (duration.seconds < 0 && duration.nanos > 0) {
|
||||
/// duration.seconds += 1;
|
||||
/// duration.nanos -= 1000000000;
|
||||
/// } else if (duration.seconds > 0 && duration.nanos < 0) {
|
||||
/// duration.seconds -= 1;
|
||||
/// duration.nanos += 1000000000;
|
||||
/// }
|
||||
///
|
||||
/// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
|
||||
///
|
||||
/// Timestamp start = ...;
|
||||
/// Duration duration = ...;
|
||||
/// Timestamp end = ...;
|
||||
///
|
||||
/// end.seconds = start.seconds + duration.seconds;
|
||||
/// end.nanos = start.nanos + duration.nanos;
|
||||
///
|
||||
/// if (end.nanos < 0) {
|
||||
/// end.seconds -= 1;
|
||||
/// end.nanos += 1000000000;
|
||||
/// } else if (end.nanos >= 1000000000) {
|
||||
/// end.seconds += 1;
|
||||
/// end.nanos -= 1000000000;
|
||||
/// }
|
||||
///
|
||||
/// Example 3: Compute Duration from datetime.timedelta in Python.
|
||||
///
|
||||
/// td = datetime.timedelta(days=3, minutes=10)
|
||||
/// duration = Duration()
|
||||
/// duration.FromTimedelta(td)
|
||||
///
|
||||
/// # JSON Mapping
|
||||
///
|
||||
/// In JSON format, the Duration type is encoded as a string rather than an
|
||||
/// object, where the string ends in the suffix "s" (indicating seconds) and
|
||||
/// is preceded by the number of seconds, with nanoseconds expressed as
|
||||
/// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
|
||||
/// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
|
||||
/// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
|
||||
/// microsecond should be expressed in JSON format as "3.000001s".
|
||||
/// </summary>
|
||||
public sealed partial class Duration : pb::IMessage<Duration>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Duration> _parser = new pb::MessageParser<Duration>(() => new Duration());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Duration> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.DurationReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Duration() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Duration(Duration other) : this() {
|
||||
seconds_ = other.seconds_;
|
||||
nanos_ = other.nanos_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Duration Clone() {
|
||||
return new Duration(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "seconds" field.</summary>
|
||||
public const int SecondsFieldNumber = 1;
|
||||
private long seconds_;
|
||||
/// <summary>
|
||||
/// Signed seconds of the span of time. Must be from -315,576,000,000
|
||||
/// to +315,576,000,000 inclusive. Note: these bounds are computed from:
|
||||
/// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public long Seconds {
|
||||
get { return seconds_; }
|
||||
set {
|
||||
seconds_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "nanos" field.</summary>
|
||||
public const int NanosFieldNumber = 2;
|
||||
private int nanos_;
|
||||
/// <summary>
|
||||
/// Signed fractions of a second at nanosecond resolution of the span
|
||||
/// of time. Durations less than one second are represented with a 0
|
||||
/// `seconds` field and a positive or negative `nanos` field. For durations
|
||||
/// of one second or more, a non-zero value for the `nanos` field must be
|
||||
/// of the same sign as the `seconds` field. Must be from -999,999,999
|
||||
/// to +999,999,999 inclusive.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int Nanos {
|
||||
get { return nanos_; }
|
||||
set {
|
||||
nanos_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Duration);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Duration other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Seconds != other.Seconds) return false;
|
||||
if (Nanos != other.Nanos) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Seconds != 0L) hash ^= Seconds.GetHashCode();
|
||||
if (Nanos != 0) hash ^= Nanos.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Seconds != 0L) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt64(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteInt32(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Seconds != 0L) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt64(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteInt32(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Seconds != 0L) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt64Size(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Duration other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Seconds != 0L) {
|
||||
Seconds = other.Seconds;
|
||||
}
|
||||
if (other.Nanos != 0) {
|
||||
Nanos = other.Nanos;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
Seconds = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Nanos = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
Seconds = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Nanos = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,270 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
// Manually-written partial class for the Duration well-known type,
|
||||
// providing a conversion to TimeSpan and convenience operators.
|
||||
public partial class Duration : ICustomDiagnosticMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of nanoseconds in a second.
|
||||
/// </summary>
|
||||
public const int NanosecondsPerSecond = 1000000000;
|
||||
/// <summary>
|
||||
/// The number of nanoseconds in a BCL tick (as used by <see cref="TimeSpan"/> and <see cref="DateTime"/>).
|
||||
/// </summary>
|
||||
public const int NanosecondsPerTick = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum permitted number of seconds.
|
||||
/// </summary>
|
||||
public const long MaxSeconds = 315576000000L;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum permitted number of seconds.
|
||||
/// </summary>
|
||||
public const long MinSeconds = -315576000000L;
|
||||
|
||||
internal const int MaxNanoseconds = NanosecondsPerSecond - 1;
|
||||
internal const int MinNanoseconds = -NanosecondsPerSecond + 1;
|
||||
|
||||
internal static bool IsNormalized(long seconds, int nanoseconds)
|
||||
{
|
||||
// Simple boundaries
|
||||
if (seconds < MinSeconds || seconds > MaxSeconds ||
|
||||
nanoseconds < MinNanoseconds || nanoseconds > MaxNanoseconds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// We only have a problem is one is strictly negative and the other is
|
||||
// strictly positive.
|
||||
return Math.Sign(seconds) * Math.Sign(nanoseconds) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="Duration"/> to a <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
/// <remarks>If the duration is not a precise number of ticks, it is truncated towards 0.</remarks>
|
||||
/// <returns>The value of this duration, as a <c>TimeSpan</c>.</returns>
|
||||
/// <exception cref="InvalidOperationException">This value isn't a valid normalized duration, as
|
||||
/// described in the documentation.</exception>
|
||||
public TimeSpan ToTimeSpan()
|
||||
{
|
||||
checked
|
||||
{
|
||||
if (!IsNormalized(Seconds, Nanos))
|
||||
{
|
||||
throw new InvalidOperationException("Duration was not a valid normalized duration");
|
||||
}
|
||||
long ticks = Seconds * TimeSpan.TicksPerSecond + Nanos / NanosecondsPerTick;
|
||||
return TimeSpan.FromTicks(ticks);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="TimeSpan"/> to a <see cref="Duration"/>.
|
||||
/// </summary>
|
||||
/// <param name="timeSpan">The <c>TimeSpan</c> to convert.</param>
|
||||
/// <returns>The value of the given <c>TimeSpan</c>, as a <c>Duration</c>.</returns>
|
||||
public static Duration FromTimeSpan(TimeSpan timeSpan)
|
||||
{
|
||||
checked
|
||||
{
|
||||
long ticks = timeSpan.Ticks;
|
||||
long seconds = ticks / TimeSpan.TicksPerSecond;
|
||||
int nanos = (int) (ticks % TimeSpan.TicksPerSecond) * NanosecondsPerTick;
|
||||
return new Duration { Seconds = seconds, Nanos = nanos };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the result of negating the duration. For example, the negation of 5 minutes is -5 minutes.
|
||||
/// </summary>
|
||||
/// <param name="value">The duration to negate. Must not be null.</param>
|
||||
/// <returns>The negated value of this duration.</returns>
|
||||
public static Duration operator -(Duration value)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(value, "value");
|
||||
checked
|
||||
{
|
||||
return Normalize(-value.Seconds, -value.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the two specified <see cref="Duration"/> values together.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The first value to add. Must not be null.</param>
|
||||
/// <param name="rhs">The second value to add. Must not be null.</param>
|
||||
/// <returns></returns>
|
||||
public static Duration operator +(Duration lhs, Duration rhs)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(lhs, "lhs");
|
||||
ProtoPreconditions.CheckNotNull(rhs, "rhs");
|
||||
checked
|
||||
{
|
||||
return Normalize(lhs.Seconds + rhs.Seconds, lhs.Nanos + rhs.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts one <see cref="Duration"/> from another.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The duration to subtract from. Must not be null.</param>
|
||||
/// <param name="rhs">The duration to subtract. Must not be null.</param>
|
||||
/// <returns>The difference between the two specified durations.</returns>
|
||||
public static Duration operator -(Duration lhs, Duration rhs)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(lhs, "lhs");
|
||||
ProtoPreconditions.CheckNotNull(rhs, "rhs");
|
||||
checked
|
||||
{
|
||||
return Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a duration with the normalized values from the given number of seconds and
|
||||
/// nanoseconds, conforming with the description in the proto file.
|
||||
/// </summary>
|
||||
internal static Duration Normalize(long seconds, int nanoseconds)
|
||||
{
|
||||
// Ensure that nanoseconds is in the range (-1,000,000,000, +1,000,000,000)
|
||||
int extraSeconds = nanoseconds / NanosecondsPerSecond;
|
||||
seconds += extraSeconds;
|
||||
nanoseconds -= extraSeconds * NanosecondsPerSecond;
|
||||
|
||||
// Now make sure that Sign(seconds) == Sign(nanoseconds) if Sign(seconds) != 0.
|
||||
if (seconds < 0 && nanoseconds > 0)
|
||||
{
|
||||
seconds += 1;
|
||||
nanoseconds -= NanosecondsPerSecond;
|
||||
}
|
||||
else if (seconds > 0 && nanoseconds < 0)
|
||||
{
|
||||
seconds -= 1;
|
||||
nanoseconds += NanosecondsPerSecond;
|
||||
}
|
||||
return new Duration { Seconds = seconds, Nanos = nanoseconds };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a duration specified in seconds/nanoseconds to a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the value is a normalized duration in the range described in <c>duration.proto</c>,
|
||||
/// <paramref name="diagnosticOnly"/> is ignored. Otherwise, if the parameter is <c>true</c>,
|
||||
/// a JSON object with a warning is returned; if it is <c>false</c>, an <see cref="InvalidOperationException"/> is thrown.
|
||||
/// </remarks>
|
||||
/// <param name="seconds">Seconds portion of the duration.</param>
|
||||
/// <param name="nanoseconds">Nanoseconds portion of the duration.</param>
|
||||
/// <param name="diagnosticOnly">Determines the handling of non-normalized values</param>
|
||||
/// <exception cref="InvalidOperationException">The represented duration is invalid, and <paramref name="diagnosticOnly"/> is <c>false</c>.</exception>
|
||||
internal static string ToJson(long seconds, int nanoseconds, bool diagnosticOnly)
|
||||
{
|
||||
if (IsNormalized(seconds, nanoseconds))
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append('"');
|
||||
// The seconds part will normally provide the minus sign if we need it, but not if it's 0...
|
||||
if (seconds == 0 && nanoseconds < 0)
|
||||
{
|
||||
builder.Append('-');
|
||||
}
|
||||
|
||||
builder.Append(seconds.ToString("d", CultureInfo.InvariantCulture));
|
||||
AppendNanoseconds(builder, Math.Abs(nanoseconds));
|
||||
builder.Append("s\"");
|
||||
return builder.ToString();
|
||||
}
|
||||
if (diagnosticOnly)
|
||||
{
|
||||
// Note: the double braces here are escaping for braces in format strings.
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
"{{ \"@warning\": \"Invalid Duration\", \"seconds\": \"{0}\", \"nanos\": {1} }}",
|
||||
seconds,
|
||||
nanoseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Non-normalized duration value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this <see cref="Duration"/> for diagnostic purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normally the returned value will be a JSON string value (including leading and trailing quotes) but
|
||||
/// when the value is non-normalized or out of range, a JSON object representation will be returned
|
||||
/// instead, including a warning. This is to avoid exceptions being thrown when trying to
|
||||
/// diagnose problems - the regular JSON formatter will still throw an exception for non-normalized
|
||||
/// values.
|
||||
/// </remarks>
|
||||
/// <returns>A string representation of this value.</returns>
|
||||
public string ToDiagnosticString()
|
||||
{
|
||||
return ToJson(Seconds, Nanos, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a number of nanoseconds to a StringBuilder. Either 0 digits are added (in which
|
||||
/// case no "." is appended), or 3 6 or 9 digits. This is internal for use in Timestamp as well
|
||||
/// as Duration.
|
||||
/// </summary>
|
||||
internal static void AppendNanoseconds(StringBuilder builder, int nanos)
|
||||
{
|
||||
if (nanos != 0)
|
||||
{
|
||||
builder.Append('.');
|
||||
// Output to 3, 6 or 9 digits.
|
||||
if (nanos % 1000000 == 0)
|
||||
{
|
||||
builder.Append((nanos / 1000000).ToString("d3", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (nanos % 1000 == 0)
|
||||
{
|
||||
builder.Append((nanos / 1000).ToString("d6", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(nanos.ToString("d9", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/empty.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/empty.proto</summary>
|
||||
public static partial class EmptyReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/empty.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static EmptyReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Chtnb29nbGUvcHJvdG9idWYvZW1wdHkucHJvdG8SD2dvb2dsZS5wcm90b2J1",
|
||||
"ZiIHCgVFbXB0eUJ9ChNjb20uZ29vZ2xlLnByb3RvYnVmQgpFbXB0eVByb3Rv",
|
||||
"UAFaLmdvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL2Vt",
|
||||
"cHR5cGL4AQGiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlw",
|
||||
"ZXNiBnByb3RvMw=="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Empty), global::LC.Google.Protobuf.WellKnownTypes.Empty.Parser, null, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// A generic empty message that you can re-use to avoid defining duplicated
|
||||
/// empty messages in your APIs. A typical example is to use it as the request
|
||||
/// or the response type of an API method. For instance:
|
||||
///
|
||||
/// service Foo {
|
||||
/// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
/// }
|
||||
///
|
||||
/// The JSON representation for `Empty` is empty JSON object `{}`.
|
||||
/// </summary>
|
||||
public sealed partial class Empty : pb::IMessage<Empty>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Empty> _parser = new pb::MessageParser<Empty>(() => new Empty());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Empty> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.EmptyReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Empty() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Empty(Empty other) : this() {
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Empty Clone() {
|
||||
return new Empty(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Empty);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Empty other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Empty other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,410 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/field_mask.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/field_mask.proto</summary>
|
||||
public static partial class FieldMaskReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/field_mask.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static FieldMaskReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"CiBnb29nbGUvcHJvdG9idWYvZmllbGRfbWFzay5wcm90bxIPZ29vZ2xlLnBy",
|
||||
"b3RvYnVmIhoKCUZpZWxkTWFzaxINCgVwYXRocxgBIAMoCUKFAQoTY29tLmdv",
|
||||
"b2dsZS5wcm90b2J1ZkIORmllbGRNYXNrUHJvdG9QAVoyZ29vZ2xlLmdvbGFu",
|
||||
"Zy5vcmcvcHJvdG9idWYvdHlwZXMva25vd24vZmllbGRtYXNrcGL4AQGiAgNH",
|
||||
"UEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNiBnByb3RvMw=="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.FieldMask), global::LC.Google.Protobuf.WellKnownTypes.FieldMask.Parser, new[]{ "Paths" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// `FieldMask` represents a set of symbolic field paths, for example:
|
||||
///
|
||||
/// paths: "f.a"
|
||||
/// paths: "f.b.d"
|
||||
///
|
||||
/// Here `f` represents a field in some root message, `a` and `b`
|
||||
/// fields in the message found in `f`, and `d` a field found in the
|
||||
/// message in `f.b`.
|
||||
///
|
||||
/// Field masks are used to specify a subset of fields that should be
|
||||
/// returned by a get operation or modified by an update operation.
|
||||
/// Field masks also have a custom JSON encoding (see below).
|
||||
///
|
||||
/// # Field Masks in Projections
|
||||
///
|
||||
/// When used in the context of a projection, a response message or
|
||||
/// sub-message is filtered by the API to only contain those fields as
|
||||
/// specified in the mask. For example, if the mask in the previous
|
||||
/// example is applied to a response message as follows:
|
||||
///
|
||||
/// f {
|
||||
/// a : 22
|
||||
/// b {
|
||||
/// d : 1
|
||||
/// x : 2
|
||||
/// }
|
||||
/// y : 13
|
||||
/// }
|
||||
/// z: 8
|
||||
///
|
||||
/// The result will not contain specific values for fields x,y and z
|
||||
/// (their value will be set to the default, and omitted in proto text
|
||||
/// output):
|
||||
///
|
||||
/// f {
|
||||
/// a : 22
|
||||
/// b {
|
||||
/// d : 1
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// A repeated field is not allowed except at the last position of a
|
||||
/// paths string.
|
||||
///
|
||||
/// If a FieldMask object is not present in a get operation, the
|
||||
/// operation applies to all fields (as if a FieldMask of all fields
|
||||
/// had been specified).
|
||||
///
|
||||
/// Note that a field mask does not necessarily apply to the
|
||||
/// top-level response message. In case of a REST get operation, the
|
||||
/// field mask applies directly to the response, but in case of a REST
|
||||
/// list operation, the mask instead applies to each individual message
|
||||
/// in the returned resource list. In case of a REST custom method,
|
||||
/// other definitions may be used. Where the mask applies will be
|
||||
/// clearly documented together with its declaration in the API. In
|
||||
/// any case, the effect on the returned resource/resources is required
|
||||
/// behavior for APIs.
|
||||
///
|
||||
/// # Field Masks in Update Operations
|
||||
///
|
||||
/// A field mask in update operations specifies which fields of the
|
||||
/// targeted resource are going to be updated. The API is required
|
||||
/// to only change the values of the fields as specified in the mask
|
||||
/// and leave the others untouched. If a resource is passed in to
|
||||
/// describe the updated values, the API ignores the values of all
|
||||
/// fields not covered by the mask.
|
||||
///
|
||||
/// If a repeated field is specified for an update operation, new values will
|
||||
/// be appended to the existing repeated field in the target resource. Note that
|
||||
/// a repeated field is only allowed in the last position of a `paths` string.
|
||||
///
|
||||
/// If a sub-message is specified in the last position of the field mask for an
|
||||
/// update operation, then new value will be merged into the existing sub-message
|
||||
/// in the target resource.
|
||||
///
|
||||
/// For example, given the target message:
|
||||
///
|
||||
/// f {
|
||||
/// b {
|
||||
/// d: 1
|
||||
/// x: 2
|
||||
/// }
|
||||
/// c: [1]
|
||||
/// }
|
||||
///
|
||||
/// And an update message:
|
||||
///
|
||||
/// f {
|
||||
/// b {
|
||||
/// d: 10
|
||||
/// }
|
||||
/// c: [2]
|
||||
/// }
|
||||
///
|
||||
/// then if the field mask is:
|
||||
///
|
||||
/// paths: ["f.b", "f.c"]
|
||||
///
|
||||
/// then the result will be:
|
||||
///
|
||||
/// f {
|
||||
/// b {
|
||||
/// d: 10
|
||||
/// x: 2
|
||||
/// }
|
||||
/// c: [1, 2]
|
||||
/// }
|
||||
///
|
||||
/// An implementation may provide options to override this default behavior for
|
||||
/// repeated and message fields.
|
||||
///
|
||||
/// In order to reset a field's value to the default, the field must
|
||||
/// be in the mask and set to the default value in the provided resource.
|
||||
/// Hence, in order to reset all fields of a resource, provide a default
|
||||
/// instance of the resource and set all fields in the mask, or do
|
||||
/// not provide a mask as described below.
|
||||
///
|
||||
/// If a field mask is not present on update, the operation applies to
|
||||
/// all fields (as if a field mask of all fields has been specified).
|
||||
/// Note that in the presence of schema evolution, this may mean that
|
||||
/// fields the client does not know and has therefore not filled into
|
||||
/// the request will be reset to their default. If this is unwanted
|
||||
/// behavior, a specific service may require a client to always specify
|
||||
/// a field mask, producing an error if not.
|
||||
///
|
||||
/// As with get operations, the location of the resource which
|
||||
/// describes the updated values in the request message depends on the
|
||||
/// operation kind. In any case, the effect of the field mask is
|
||||
/// required to be honored by the API.
|
||||
///
|
||||
/// ## Considerations for HTTP REST
|
||||
///
|
||||
/// The HTTP kind of an update operation which uses a field mask must
|
||||
/// be set to PATCH instead of PUT in order to satisfy HTTP semantics
|
||||
/// (PUT must only be used for full updates).
|
||||
///
|
||||
/// # JSON Encoding of Field Masks
|
||||
///
|
||||
/// In JSON, a field mask is encoded as a single string where paths are
|
||||
/// separated by a comma. Fields name in each path are converted
|
||||
/// to/from lower-camel naming conventions.
|
||||
///
|
||||
/// As an example, consider the following message declarations:
|
||||
///
|
||||
/// message Profile {
|
||||
/// User user = 1;
|
||||
/// Photo photo = 2;
|
||||
/// }
|
||||
/// message User {
|
||||
/// string display_name = 1;
|
||||
/// string address = 2;
|
||||
/// }
|
||||
///
|
||||
/// In proto a field mask for `Profile` may look as such:
|
||||
///
|
||||
/// mask {
|
||||
/// paths: "user.display_name"
|
||||
/// paths: "photo"
|
||||
/// }
|
||||
///
|
||||
/// In JSON, the same mask is represented as below:
|
||||
///
|
||||
/// {
|
||||
/// mask: "user.displayName,photo"
|
||||
/// }
|
||||
///
|
||||
/// # Field Masks and Oneof Fields
|
||||
///
|
||||
/// Field masks treat fields in oneofs just as regular fields. Consider the
|
||||
/// following message:
|
||||
///
|
||||
/// message SampleMessage {
|
||||
/// oneof test_oneof {
|
||||
/// string name = 4;
|
||||
/// SubMessage sub_message = 9;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// The field mask can be:
|
||||
///
|
||||
/// mask {
|
||||
/// paths: "name"
|
||||
/// }
|
||||
///
|
||||
/// Or:
|
||||
///
|
||||
/// mask {
|
||||
/// paths: "sub_message"
|
||||
/// }
|
||||
///
|
||||
/// Note that oneof type names ("test_oneof" in this case) cannot be used in
|
||||
/// paths.
|
||||
///
|
||||
/// ## Field Mask Verification
|
||||
///
|
||||
/// The implementation of any API method which has a FieldMask type field in the
|
||||
/// request should verify the included field paths, and return an
|
||||
/// `INVALID_ARGUMENT` error if any path is unmappable.
|
||||
/// </summary>
|
||||
public sealed partial class FieldMask : pb::IMessage<FieldMask>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<FieldMask> _parser = new pb::MessageParser<FieldMask>(() => new FieldMask());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<FieldMask> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.FieldMaskReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public FieldMask() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public FieldMask(FieldMask other) : this() {
|
||||
paths_ = other.paths_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public FieldMask Clone() {
|
||||
return new FieldMask(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "paths" field.</summary>
|
||||
public const int PathsFieldNumber = 1;
|
||||
private static readonly pb::FieldCodec<string> _repeated_paths_codec
|
||||
= pb::FieldCodec.ForString(10);
|
||||
private readonly pbc::RepeatedField<string> paths_ = new pbc::RepeatedField<string>();
|
||||
/// <summary>
|
||||
/// The set of field mask paths.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public pbc::RepeatedField<string> Paths {
|
||||
get { return paths_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as FieldMask);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(FieldMask other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if(!paths_.Equals(other.paths_)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= paths_.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
paths_.WriteTo(output, _repeated_paths_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
paths_.WriteTo(ref output, _repeated_paths_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += paths_.CalculateSize(_repeated_paths_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(FieldMask other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
paths_.Add(other.paths_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
paths_.AddEntriesFrom(input, _repeated_paths_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
paths_.AddEntriesFrom(ref input, _repeated_paths_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,370 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2016 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
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LC.Google.Protobuf.Reflection;
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
// Manually-written partial class for the FieldMask well-known type.
|
||||
public partial class FieldMask : ICustomDiagnosticMessage
|
||||
{
|
||||
private const char FIELD_PATH_SEPARATOR = ',';
|
||||
private const char FIELD_SEPARATOR_REGEX = '.';
|
||||
|
||||
/// <summary>
|
||||
/// Converts a field mask specified by paths to a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the value is a normalized duration in the range described in <c>field_mask.proto</c>,
|
||||
/// <paramref name="diagnosticOnly"/> is ignored. Otherwise, if the parameter is <c>true</c>,
|
||||
/// a JSON object with a warning is returned; if it is <c>false</c>, an <see cref="InvalidOperationException"/> is thrown.
|
||||
/// </remarks>
|
||||
/// <param name="paths">Paths in the field mask</param>
|
||||
/// <param name="diagnosticOnly">Determines the handling of non-normalized values</param>
|
||||
/// <exception cref="InvalidOperationException">The represented field mask is invalid, and <paramref name="diagnosticOnly"/> is <c>false</c>.</exception>
|
||||
internal static string ToJson(IList<string> paths, bool diagnosticOnly)
|
||||
{
|
||||
var firstInvalid = paths.FirstOrDefault(p => !IsPathValid(p));
|
||||
if (firstInvalid == null)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
#if NET35
|
||||
var query = paths.Select(JsonFormatter.ToJsonName);
|
||||
JsonFormatter.WriteString(writer, string.Join(",", query.ToArray()));
|
||||
#else
|
||||
JsonFormatter.WriteString(writer, string.Join(",", paths.Select(JsonFormatter.ToJsonName)));
|
||||
#endif
|
||||
return writer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (diagnosticOnly)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
writer.Write("{ \"@warning\": \"Invalid FieldMask\", \"paths\": ");
|
||||
JsonFormatter.Default.WriteList(writer, (IList)paths);
|
||||
writer.Write(" }");
|
||||
return writer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {firstInvalid}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this <see cref="FieldMask"/> for diagnostic purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normally the returned value will be a JSON string value (including leading and trailing quotes) but
|
||||
/// when the value is non-normalized or out of range, a JSON object representation will be returned
|
||||
/// instead, including a warning. This is to avoid exceptions being thrown when trying to
|
||||
/// diagnose problems - the regular JSON formatter will still throw an exception for non-normalized
|
||||
/// values.
|
||||
/// </remarks>
|
||||
/// <returns>A string representation of this value.</returns>
|
||||
public string ToDiagnosticString()
|
||||
{
|
||||
return ToJson(Paths, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses from a string to a FieldMask.
|
||||
/// </summary>
|
||||
public static FieldMask FromString(string value)
|
||||
{
|
||||
return FromStringEnumerable<Empty>(new List<string>(value.Split(FIELD_PATH_SEPARATOR)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses from a string to a FieldMask and validates all field paths.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static FieldMask FromString<T>(string value) where T : IMessage
|
||||
{
|
||||
return FromStringEnumerable<T>(new List<string>(value.Split(FIELD_PATH_SEPARATOR)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a FieldMask for a list of field paths in a certain type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static FieldMask FromStringEnumerable<T>(IEnumerable<string> paths) where T : IMessage
|
||||
{
|
||||
var mask = new FieldMask();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (path.Length == 0)
|
||||
{
|
||||
// Ignore empty field paths.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(T) != typeof(Empty)
|
||||
&& !IsValid<T>(path))
|
||||
{
|
||||
throw new InvalidProtocolBufferException(path + " is not a valid path for " + typeof(T));
|
||||
}
|
||||
|
||||
mask.Paths.Add(path);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a FieldMask from the passed field numbers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static FieldMask FromFieldNumbers<T>(params int[] fieldNumbers) where T : IMessage
|
||||
{
|
||||
return FromFieldNumbers<T>((IEnumerable<int>)fieldNumbers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a FieldMask from the passed field numbers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static FieldMask FromFieldNumbers<T>(IEnumerable<int> fieldNumbers) where T : IMessage
|
||||
{
|
||||
var descriptor = Activator.CreateInstance<T>().Descriptor;
|
||||
|
||||
var mask = new FieldMask();
|
||||
foreach (var fieldNumber in fieldNumbers)
|
||||
{
|
||||
var field = descriptor.FindFieldByNumber(fieldNumber);
|
||||
if (field == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{fieldNumber} is not a valid field number for {descriptor.Name}");
|
||||
}
|
||||
|
||||
mask.Paths.Add(field.Name);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given path is valid for a field mask.
|
||||
/// </summary>
|
||||
/// <returns>true if the path is valid; false otherwise</returns>
|
||||
private static bool IsPathValid(string input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
char c = input[i];
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (c == '_' && i < input.Length - 1)
|
||||
{
|
||||
char next = input[i + 1];
|
||||
if (next < 'a' || next > 'z')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether paths in a given fields mask are valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static bool IsValid<T>(FieldMask fieldMask) where T : IMessage
|
||||
{
|
||||
var descriptor = Activator.CreateInstance<T>().Descriptor;
|
||||
|
||||
return IsValid(descriptor, fieldMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether paths in a given fields mask are valid.
|
||||
/// </summary>
|
||||
public static bool IsValid(MessageDescriptor descriptor, FieldMask fieldMask)
|
||||
{
|
||||
foreach (var path in fieldMask.Paths)
|
||||
{
|
||||
if (!IsValid(descriptor, path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a given field path is valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to validate the field paths against.</typeparam>
|
||||
public static bool IsValid<T>(string path) where T : IMessage
|
||||
{
|
||||
var descriptor = Activator.CreateInstance<T>().Descriptor;
|
||||
|
||||
return IsValid(descriptor, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether paths in a given fields mask are valid.
|
||||
/// </summary>
|
||||
public static bool IsValid(MessageDescriptor descriptor, string path)
|
||||
{
|
||||
var parts = path.Split(FIELD_SEPARATOR_REGEX);
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var name in parts)
|
||||
{
|
||||
var field = descriptor?.FindFieldByName(name);
|
||||
if (field == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!field.IsRepeated
|
||||
&& field.FieldType == FieldType.Message)
|
||||
{
|
||||
descriptor = field.MessageType;
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor = null;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this FieldMask to its canonical form. In the canonical form of a
|
||||
/// FieldMask, all field paths are sorted alphabetically and redundant field
|
||||
/// paths are removed.
|
||||
/// </summary>
|
||||
public FieldMask Normalize()
|
||||
{
|
||||
return new FieldMaskTree(this).ToFieldMask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a union of two or more FieldMasks.
|
||||
/// </summary>
|
||||
public FieldMask Union(params FieldMask[] otherMasks)
|
||||
{
|
||||
var maskTree = new FieldMaskTree(this);
|
||||
foreach (var mask in otherMasks)
|
||||
{
|
||||
maskTree.MergeFromFieldMask(mask);
|
||||
}
|
||||
|
||||
return maskTree.ToFieldMask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the intersection of two FieldMasks.
|
||||
/// </summary>
|
||||
public FieldMask Intersection(FieldMask additionalMask)
|
||||
{
|
||||
var tree = new FieldMaskTree(this);
|
||||
var result = new FieldMaskTree();
|
||||
foreach (var path in additionalMask.Paths)
|
||||
{
|
||||
tree.IntersectFieldPath(path, result);
|
||||
}
|
||||
|
||||
return result.ToFieldMask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges fields specified by this FieldMask from one message to another with the
|
||||
/// specified merge options.
|
||||
/// </summary>
|
||||
public void Merge(IMessage source, IMessage destination, MergeOptions options)
|
||||
{
|
||||
new FieldMaskTree(this).Merge(source, destination, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges fields specified by this FieldMask from one message to another.
|
||||
/// </summary>
|
||||
public void Merge(IMessage source, IMessage destination)
|
||||
{
|
||||
Merge(source, destination, new MergeOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options to customize merging behavior.
|
||||
/// </summary>
|
||||
public sealed class MergeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to replace message fields(i.e., discard existing content in
|
||||
/// destination message fields) when merging.
|
||||
/// Default behavior is to merge the source message field into the
|
||||
/// destination message field.
|
||||
/// </summary>
|
||||
public bool ReplaceMessageFields { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to replace repeated fields (i.e., discard existing content in
|
||||
/// destination repeated fields) when merging.
|
||||
/// Default behavior is to append elements from source repeated field to the
|
||||
/// destination repeated field.
|
||||
/// </summary>
|
||||
public bool ReplaceRepeatedFields { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to replace primitive (non-repeated and non-message) fields in
|
||||
/// destination message fields with the source primitive fields (i.e., if the
|
||||
/// field is set in the source, the value is copied to the
|
||||
/// destination; if the field is unset in the source, the field is cleared
|
||||
/// from the destination) when merging.
|
||||
///
|
||||
/// Default behavior is to always set the value of the source primitive
|
||||
/// field to the destination primitive field, and if the source field is
|
||||
/// unset, the default value of the source field is copied to the
|
||||
/// destination.
|
||||
/// </summary>
|
||||
public bool ReplacePrimitiveFields { get; set; } = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/source_context.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/source_context.proto</summary>
|
||||
public static partial class SourceContextReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/source_context.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static SourceContextReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"CiRnb29nbGUvcHJvdG9idWYvc291cmNlX2NvbnRleHQucHJvdG8SD2dvb2ds",
|
||||
"ZS5wcm90b2J1ZiIiCg1Tb3VyY2VDb250ZXh0EhEKCWZpbGVfbmFtZRgBIAEo",
|
||||
"CUKKAQoTY29tLmdvb2dsZS5wcm90b2J1ZkISU291cmNlQ29udGV4dFByb3Rv",
|
||||
"UAFaNmdvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL3Nv",
|
||||
"dXJjZWNvbnRleHRwYqICA0dQQqoCHkdvb2dsZS5Qcm90b2J1Zi5XZWxsS25v",
|
||||
"d25UeXBlc2IGcHJvdG8z"));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.SourceContext), global::LC.Google.Protobuf.WellKnownTypes.SourceContext.Parser, new[]{ "FileName" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// `SourceContext` represents information about the source of a
|
||||
/// protobuf element, like the file in which it is defined.
|
||||
/// </summary>
|
||||
public sealed partial class SourceContext : pb::IMessage<SourceContext>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<SourceContext> _parser = new pb::MessageParser<SourceContext>(() => new SourceContext());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<SourceContext> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.SourceContextReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public SourceContext() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public SourceContext(SourceContext other) : this() {
|
||||
fileName_ = other.fileName_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public SourceContext Clone() {
|
||||
return new SourceContext(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "file_name" field.</summary>
|
||||
public const int FileNameFieldNumber = 1;
|
||||
private string fileName_ = "";
|
||||
/// <summary>
|
||||
/// The path-qualified name of the .proto file that contained the associated
|
||||
/// protobuf element. For example: `"google/protobuf/source_context.proto"`.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public string FileName {
|
||||
get { return fileName_; }
|
||||
set {
|
||||
fileName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as SourceContext);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(SourceContext other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (FileName != other.FileName) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (FileName.Length != 0) hash ^= FileName.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (FileName.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(FileName);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (FileName.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(FileName);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (FileName.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(FileName);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(SourceContext other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.FileName.Length != 0) {
|
||||
FileName = other.FileName;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
FileName = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
FileName = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,866 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/struct.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/struct.proto</summary>
|
||||
public static partial class StructReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/struct.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static StructReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Chxnb29nbGUvcHJvdG9idWYvc3RydWN0LnByb3RvEg9nb29nbGUucHJvdG9i",
|
||||
"dWYihAEKBlN0cnVjdBIzCgZmaWVsZHMYASADKAsyIy5nb29nbGUucHJvdG9i",
|
||||
"dWYuU3RydWN0LkZpZWxkc0VudHJ5GkUKC0ZpZWxkc0VudHJ5EgsKA2tleRgB",
|
||||
"IAEoCRIlCgV2YWx1ZRgCIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5WYWx1ZToC",
|
||||
"OAEi6gEKBVZhbHVlEjAKCm51bGxfdmFsdWUYASABKA4yGi5nb29nbGUucHJv",
|
||||
"dG9idWYuTnVsbFZhbHVlSAASFgoMbnVtYmVyX3ZhbHVlGAIgASgBSAASFgoM",
|
||||
"c3RyaW5nX3ZhbHVlGAMgASgJSAASFAoKYm9vbF92YWx1ZRgEIAEoCEgAEi8K",
|
||||
"DHN0cnVjdF92YWx1ZRgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RI",
|
||||
"ABIwCgpsaXN0X3ZhbHVlGAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLkxpc3RW",
|
||||
"YWx1ZUgAQgYKBGtpbmQiMwoJTGlzdFZhbHVlEiYKBnZhbHVlcxgBIAMoCzIW",
|
||||
"Lmdvb2dsZS5wcm90b2J1Zi5WYWx1ZSobCglOdWxsVmFsdWUSDgoKTlVMTF9W",
|
||||
"QUxVRRAAQn8KE2NvbS5nb29nbGUucHJvdG9idWZCC1N0cnVjdFByb3RvUAFa",
|
||||
"L2dvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL3N0cnVj",
|
||||
"dHBi+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVz",
|
||||
"YgZwcm90bzM="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::LC.Google.Protobuf.WellKnownTypes.NullValue), }, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Struct), global::LC.Google.Protobuf.WellKnownTypes.Struct.Parser, new[]{ "Fields" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, }),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Value), global::LC.Google.Protobuf.WellKnownTypes.Value.Parser, new[]{ "NullValue", "NumberValue", "StringValue", "BoolValue", "StructValue", "ListValue" }, new[]{ "Kind" }, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.ListValue), global::LC.Google.Protobuf.WellKnownTypes.ListValue.Parser, new[]{ "Values" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Enums
|
||||
/// <summary>
|
||||
/// `NullValue` is a singleton enumeration to represent the null value for the
|
||||
/// `Value` type union.
|
||||
///
|
||||
/// The JSON representation for `NullValue` is JSON `null`.
|
||||
/// </summary>
|
||||
public enum NullValue {
|
||||
/// <summary>
|
||||
/// Null value.
|
||||
/// </summary>
|
||||
[pbr::OriginalName("NULL_VALUE")] NullValue = 0,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// `Struct` represents a structured data value, consisting of fields
|
||||
/// which map to dynamically typed values. In some languages, `Struct`
|
||||
/// might be supported by a native representation. For example, in
|
||||
/// scripting languages like JS a struct is represented as an
|
||||
/// object. The details of that representation are described together
|
||||
/// with the proto support for the language.
|
||||
///
|
||||
/// The JSON representation for `Struct` is JSON object.
|
||||
/// </summary>
|
||||
public sealed partial class Struct : pb::IMessage<Struct>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Struct> _parser = new pb::MessageParser<Struct>(() => new Struct());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Struct> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.StructReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Struct() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Struct(Struct other) : this() {
|
||||
fields_ = other.fields_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Struct Clone() {
|
||||
return new Struct(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "fields" field.</summary>
|
||||
public const int FieldsFieldNumber = 1;
|
||||
private static readonly pbc::MapField<string, global::LC.Google.Protobuf.WellKnownTypes.Value>.Codec _map_fields_codec
|
||||
= new pbc::MapField<string, global::LC.Google.Protobuf.WellKnownTypes.Value>.Codec(pb::FieldCodec.ForString(10, ""), pb::FieldCodec.ForMessage(18, global::LC.Google.Protobuf.WellKnownTypes.Value.Parser), 10);
|
||||
private readonly pbc::MapField<string, global::LC.Google.Protobuf.WellKnownTypes.Value> fields_ = new pbc::MapField<string, global::LC.Google.Protobuf.WellKnownTypes.Value>();
|
||||
/// <summary>
|
||||
/// Unordered map of dynamically typed values.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public pbc::MapField<string, global::LC.Google.Protobuf.WellKnownTypes.Value> Fields {
|
||||
get { return fields_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Struct);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Struct other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (!Fields.Equals(other.Fields)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= Fields.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
fields_.WriteTo(output, _map_fields_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
fields_.WriteTo(ref output, _map_fields_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += fields_.CalculateSize(_map_fields_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Struct other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
fields_.Add(other.fields_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
fields_.AddEntriesFrom(input, _map_fields_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
fields_.AddEntriesFrom(ref input, _map_fields_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `Value` represents a dynamically typed value which can be either
|
||||
/// null, a number, a string, a boolean, a recursive struct value, or a
|
||||
/// list of values. A producer of value is expected to set one of that
|
||||
/// variants, absence of any variant indicates an error.
|
||||
///
|
||||
/// The JSON representation for `Value` is JSON value.
|
||||
/// </summary>
|
||||
public sealed partial class Value : pb::IMessage<Value>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Value> _parser = new pb::MessageParser<Value>(() => new Value());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Value> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.StructReflection.Descriptor.MessageTypes[1]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Value() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Value(Value other) : this() {
|
||||
switch (other.KindCase) {
|
||||
case KindOneofCase.NullValue:
|
||||
NullValue = other.NullValue;
|
||||
break;
|
||||
case KindOneofCase.NumberValue:
|
||||
NumberValue = other.NumberValue;
|
||||
break;
|
||||
case KindOneofCase.StringValue:
|
||||
StringValue = other.StringValue;
|
||||
break;
|
||||
case KindOneofCase.BoolValue:
|
||||
BoolValue = other.BoolValue;
|
||||
break;
|
||||
case KindOneofCase.StructValue:
|
||||
StructValue = other.StructValue.Clone();
|
||||
break;
|
||||
case KindOneofCase.ListValue:
|
||||
ListValue = other.ListValue.Clone();
|
||||
break;
|
||||
}
|
||||
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Value Clone() {
|
||||
return new Value(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "null_value" field.</summary>
|
||||
public const int NullValueFieldNumber = 1;
|
||||
/// <summary>
|
||||
/// Represents a null value.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public global::LC.Google.Protobuf.WellKnownTypes.NullValue NullValue {
|
||||
get { return kindCase_ == KindOneofCase.NullValue ? (global::LC.Google.Protobuf.WellKnownTypes.NullValue) kind_ : global::LC.Google.Protobuf.WellKnownTypes.NullValue.NullValue; }
|
||||
set {
|
||||
kind_ = value;
|
||||
kindCase_ = KindOneofCase.NullValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "number_value" field.</summary>
|
||||
public const int NumberValueFieldNumber = 2;
|
||||
/// <summary>
|
||||
/// Represents a double value.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public double NumberValue {
|
||||
get { return kindCase_ == KindOneofCase.NumberValue ? (double) kind_ : 0D; }
|
||||
set {
|
||||
kind_ = value;
|
||||
kindCase_ = KindOneofCase.NumberValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "string_value" field.</summary>
|
||||
public const int StringValueFieldNumber = 3;
|
||||
/// <summary>
|
||||
/// Represents a string value.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public string StringValue {
|
||||
get { return kindCase_ == KindOneofCase.StringValue ? (string) kind_ : ""; }
|
||||
set {
|
||||
kind_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
kindCase_ = KindOneofCase.StringValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "bool_value" field.</summary>
|
||||
public const int BoolValueFieldNumber = 4;
|
||||
/// <summary>
|
||||
/// Represents a boolean value.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool BoolValue {
|
||||
get { return kindCase_ == KindOneofCase.BoolValue ? (bool) kind_ : false; }
|
||||
set {
|
||||
kind_ = value;
|
||||
kindCase_ = KindOneofCase.BoolValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "struct_value" field.</summary>
|
||||
public const int StructValueFieldNumber = 5;
|
||||
/// <summary>
|
||||
/// Represents a structured value.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public global::LC.Google.Protobuf.WellKnownTypes.Struct StructValue {
|
||||
get { return kindCase_ == KindOneofCase.StructValue ? (global::LC.Google.Protobuf.WellKnownTypes.Struct) kind_ : null; }
|
||||
set {
|
||||
kind_ = value;
|
||||
kindCase_ = value == null ? KindOneofCase.None : KindOneofCase.StructValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "list_value" field.</summary>
|
||||
public const int ListValueFieldNumber = 6;
|
||||
/// <summary>
|
||||
/// Represents a repeated `Value`.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public global::LC.Google.Protobuf.WellKnownTypes.ListValue ListValue {
|
||||
get { return kindCase_ == KindOneofCase.ListValue ? (global::LC.Google.Protobuf.WellKnownTypes.ListValue) kind_ : null; }
|
||||
set {
|
||||
kind_ = value;
|
||||
kindCase_ = value == null ? KindOneofCase.None : KindOneofCase.ListValue;
|
||||
}
|
||||
}
|
||||
|
||||
private object kind_;
|
||||
/// <summary>Enum of possible cases for the "kind" oneof.</summary>
|
||||
public enum KindOneofCase {
|
||||
None = 0,
|
||||
NullValue = 1,
|
||||
NumberValue = 2,
|
||||
StringValue = 3,
|
||||
BoolValue = 4,
|
||||
StructValue = 5,
|
||||
ListValue = 6,
|
||||
}
|
||||
private KindOneofCase kindCase_ = KindOneofCase.None;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public KindOneofCase KindCase {
|
||||
get { return kindCase_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void ClearKind() {
|
||||
kindCase_ = KindOneofCase.None;
|
||||
kind_ = null;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Value);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Value other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (NullValue != other.NullValue) return false;
|
||||
if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(NumberValue, other.NumberValue)) return false;
|
||||
if (StringValue != other.StringValue) return false;
|
||||
if (BoolValue != other.BoolValue) return false;
|
||||
if (!object.Equals(StructValue, other.StructValue)) return false;
|
||||
if (!object.Equals(ListValue, other.ListValue)) return false;
|
||||
if (KindCase != other.KindCase) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (kindCase_ == KindOneofCase.NullValue) hash ^= NullValue.GetHashCode();
|
||||
if (kindCase_ == KindOneofCase.NumberValue) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(NumberValue);
|
||||
if (kindCase_ == KindOneofCase.StringValue) hash ^= StringValue.GetHashCode();
|
||||
if (kindCase_ == KindOneofCase.BoolValue) hash ^= BoolValue.GetHashCode();
|
||||
if (kindCase_ == KindOneofCase.StructValue) hash ^= StructValue.GetHashCode();
|
||||
if (kindCase_ == KindOneofCase.ListValue) hash ^= ListValue.GetHashCode();
|
||||
hash ^= (int) kindCase_;
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (kindCase_ == KindOneofCase.NullValue) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteEnum((int) NullValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.NumberValue) {
|
||||
output.WriteRawTag(17);
|
||||
output.WriteDouble(NumberValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StringValue) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(StringValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.BoolValue) {
|
||||
output.WriteRawTag(32);
|
||||
output.WriteBool(BoolValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StructValue) {
|
||||
output.WriteRawTag(42);
|
||||
output.WriteMessage(StructValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.ListValue) {
|
||||
output.WriteRawTag(50);
|
||||
output.WriteMessage(ListValue);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (kindCase_ == KindOneofCase.NullValue) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteEnum((int) NullValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.NumberValue) {
|
||||
output.WriteRawTag(17);
|
||||
output.WriteDouble(NumberValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StringValue) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(StringValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.BoolValue) {
|
||||
output.WriteRawTag(32);
|
||||
output.WriteBool(BoolValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StructValue) {
|
||||
output.WriteRawTag(42);
|
||||
output.WriteMessage(StructValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.ListValue) {
|
||||
output.WriteRawTag(50);
|
||||
output.WriteMessage(ListValue);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (kindCase_ == KindOneofCase.NullValue) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) NullValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.NumberValue) {
|
||||
size += 1 + 8;
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StringValue) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(StringValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.BoolValue) {
|
||||
size += 1 + 1;
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.StructValue) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeMessageSize(StructValue);
|
||||
}
|
||||
if (kindCase_ == KindOneofCase.ListValue) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeMessageSize(ListValue);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Value other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
switch (other.KindCase) {
|
||||
case KindOneofCase.NullValue:
|
||||
NullValue = other.NullValue;
|
||||
break;
|
||||
case KindOneofCase.NumberValue:
|
||||
NumberValue = other.NumberValue;
|
||||
break;
|
||||
case KindOneofCase.StringValue:
|
||||
StringValue = other.StringValue;
|
||||
break;
|
||||
case KindOneofCase.BoolValue:
|
||||
BoolValue = other.BoolValue;
|
||||
break;
|
||||
case KindOneofCase.StructValue:
|
||||
if (StructValue == null) {
|
||||
StructValue = new global::LC.Google.Protobuf.WellKnownTypes.Struct();
|
||||
}
|
||||
StructValue.MergeFrom(other.StructValue);
|
||||
break;
|
||||
case KindOneofCase.ListValue:
|
||||
if (ListValue == null) {
|
||||
ListValue = new global::LC.Google.Protobuf.WellKnownTypes.ListValue();
|
||||
}
|
||||
ListValue.MergeFrom(other.ListValue);
|
||||
break;
|
||||
}
|
||||
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
kind_ = input.ReadEnum();
|
||||
kindCase_ = KindOneofCase.NullValue;
|
||||
break;
|
||||
}
|
||||
case 17: {
|
||||
NumberValue = input.ReadDouble();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
StringValue = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 32: {
|
||||
BoolValue = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
global::LC.Google.Protobuf.WellKnownTypes.Struct subBuilder = new global::LC.Google.Protobuf.WellKnownTypes.Struct();
|
||||
if (kindCase_ == KindOneofCase.StructValue) {
|
||||
subBuilder.MergeFrom(StructValue);
|
||||
}
|
||||
input.ReadMessage(subBuilder);
|
||||
StructValue = subBuilder;
|
||||
break;
|
||||
}
|
||||
case 50: {
|
||||
global::LC.Google.Protobuf.WellKnownTypes.ListValue subBuilder = new global::LC.Google.Protobuf.WellKnownTypes.ListValue();
|
||||
if (kindCase_ == KindOneofCase.ListValue) {
|
||||
subBuilder.MergeFrom(ListValue);
|
||||
}
|
||||
input.ReadMessage(subBuilder);
|
||||
ListValue = subBuilder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
kind_ = input.ReadEnum();
|
||||
kindCase_ = KindOneofCase.NullValue;
|
||||
break;
|
||||
}
|
||||
case 17: {
|
||||
NumberValue = input.ReadDouble();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
StringValue = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 32: {
|
||||
BoolValue = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
global::LC.Google.Protobuf.WellKnownTypes.Struct subBuilder = new global::LC.Google.Protobuf.WellKnownTypes.Struct();
|
||||
if (kindCase_ == KindOneofCase.StructValue) {
|
||||
subBuilder.MergeFrom(StructValue);
|
||||
}
|
||||
input.ReadMessage(subBuilder);
|
||||
StructValue = subBuilder;
|
||||
break;
|
||||
}
|
||||
case 50: {
|
||||
global::LC.Google.Protobuf.WellKnownTypes.ListValue subBuilder = new global::LC.Google.Protobuf.WellKnownTypes.ListValue();
|
||||
if (kindCase_ == KindOneofCase.ListValue) {
|
||||
subBuilder.MergeFrom(ListValue);
|
||||
}
|
||||
input.ReadMessage(subBuilder);
|
||||
ListValue = subBuilder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `ListValue` is a wrapper around a repeated field of values.
|
||||
///
|
||||
/// The JSON representation for `ListValue` is JSON array.
|
||||
/// </summary>
|
||||
public sealed partial class ListValue : pb::IMessage<ListValue>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<ListValue> _parser = new pb::MessageParser<ListValue>(() => new ListValue());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<ListValue> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.StructReflection.Descriptor.MessageTypes[2]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public ListValue() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public ListValue(ListValue other) : this() {
|
||||
values_ = other.values_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public ListValue Clone() {
|
||||
return new ListValue(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "values" field.</summary>
|
||||
public const int ValuesFieldNumber = 1;
|
||||
private static readonly pb::FieldCodec<global::LC.Google.Protobuf.WellKnownTypes.Value> _repeated_values_codec
|
||||
= pb::FieldCodec.ForMessage(10, global::LC.Google.Protobuf.WellKnownTypes.Value.Parser);
|
||||
private readonly pbc::RepeatedField<global::LC.Google.Protobuf.WellKnownTypes.Value> values_ = new pbc::RepeatedField<global::LC.Google.Protobuf.WellKnownTypes.Value>();
|
||||
/// <summary>
|
||||
/// Repeated field of dynamically typed values.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public pbc::RepeatedField<global::LC.Google.Protobuf.WellKnownTypes.Value> Values {
|
||||
get { return values_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as ListValue);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(ListValue other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if(!values_.Equals(other.values_)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= values_.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
values_.WriteTo(output, _repeated_values_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
values_.WriteTo(ref output, _repeated_values_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += values_.CalculateSize(_repeated_values_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(ListValue other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
values_.Add(other.values_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
values_.AddEntriesFrom(input, _repeated_values_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
values_.AddEntriesFrom(ref input, _repeated_values_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,76 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods on BCL time-related types, converting to protobuf types.
|
||||
/// </summary>
|
||||
public static class TimeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="DateTime"/> to a <see cref="Timestamp"/>.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The date and time to convert to a timestamp.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="dateTime"/> value has a <see cref="DateTime.Kind"/>other than <c>Utc</c>.</exception>
|
||||
/// <returns>The converted timestamp.</returns>
|
||||
public static Timestamp ToTimestamp(this DateTime dateTime)
|
||||
{
|
||||
return Timestamp.FromDateTime(dateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="DateTimeOffset"/> to a <see cref="Timestamp"/>
|
||||
/// </summary>
|
||||
/// <remarks>The offset is taken into consideration when converting the value (so the same instant in time
|
||||
/// is represented) but is not a separate part of the resulting value. In other words, there is no
|
||||
/// roundtrip operation to retrieve the original <c>DateTimeOffset</c>.</remarks>
|
||||
/// <param name="dateTimeOffset">The date and time (with UTC offset) to convert to a timestamp.</param>
|
||||
/// <returns>The converted timestamp.</returns>
|
||||
public static Timestamp ToTimestamp(this DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
return Timestamp.FromDateTimeOffset(dateTimeOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="TimeSpan"/> to a <see cref="Duration"/>.
|
||||
/// </summary>
|
||||
/// <param name="timeSpan">The time span to convert.</param>
|
||||
/// <returns>The converted duration.</returns>
|
||||
public static Duration ToDuration(this TimeSpan timeSpan)
|
||||
{
|
||||
return Duration.FromTimeSpan(timeSpan);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: google/protobuf/timestamp.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::LC.Google.Protobuf;
|
||||
using pbc = global::LC.Google.Protobuf.Collections;
|
||||
using pbr = global::LC.Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
namespace LC.Google.Protobuf.WellKnownTypes {
|
||||
|
||||
/// <summary>Holder for reflection information generated from google/protobuf/timestamp.proto</summary>
|
||||
public static partial class TimestampReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for google/protobuf/timestamp.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static TimestampReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Ch9nb29nbGUvcHJvdG9idWYvdGltZXN0YW1wLnByb3RvEg9nb29nbGUucHJv",
|
||||
"dG9idWYiKwoJVGltZXN0YW1wEg8KB3NlY29uZHMYASABKAMSDQoFbmFub3MY",
|
||||
"AiABKAVChQEKE2NvbS5nb29nbGUucHJvdG9idWZCDlRpbWVzdGFtcFByb3Rv",
|
||||
"UAFaMmdvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL3Rp",
|
||||
"bWVzdGFtcHBi+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93",
|
||||
"blR5cGVzYgZwcm90bzM="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::LC.Google.Protobuf.WellKnownTypes.Timestamp), global::LC.Google.Protobuf.WellKnownTypes.Timestamp.Parser, new[]{ "Seconds", "Nanos" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
/// <summary>
|
||||
/// A Timestamp represents a point in time independent of any time zone or local
|
||||
/// calendar, encoded as a count of seconds and fractions of seconds at
|
||||
/// nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||
/// January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||
/// Gregorian calendar backwards to year one.
|
||||
///
|
||||
/// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
|
||||
/// second table is needed for interpretation, using a [24-hour linear
|
||||
/// smear](https://developers.google.com/time/smear).
|
||||
///
|
||||
/// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
|
||||
/// restricting to that range, we ensure that we can convert to and from [RFC
|
||||
/// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Example 1: Compute Timestamp from POSIX `time()`.
|
||||
///
|
||||
/// Timestamp timestamp;
|
||||
/// timestamp.set_seconds(time(NULL));
|
||||
/// timestamp.set_nanos(0);
|
||||
///
|
||||
/// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
|
||||
///
|
||||
/// struct timeval tv;
|
||||
/// gettimeofday(&tv, NULL);
|
||||
///
|
||||
/// Timestamp timestamp;
|
||||
/// timestamp.set_seconds(tv.tv_sec);
|
||||
/// timestamp.set_nanos(tv.tv_usec * 1000);
|
||||
///
|
||||
/// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
|
||||
///
|
||||
/// FILETIME ft;
|
||||
/// GetSystemTimeAsFileTime(&ft);
|
||||
/// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
///
|
||||
/// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
|
||||
/// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
|
||||
/// Timestamp timestamp;
|
||||
/// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
|
||||
/// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
|
||||
///
|
||||
/// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
|
||||
///
|
||||
/// long millis = System.currentTimeMillis();
|
||||
///
|
||||
/// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||
/// .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||
///
|
||||
/// Example 5: Compute Timestamp from Java `Instant.now()`.
|
||||
///
|
||||
/// Instant now = Instant.now();
|
||||
///
|
||||
/// Timestamp timestamp =
|
||||
/// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
|
||||
/// .setNanos(now.getNano()).build();
|
||||
///
|
||||
/// Example 6: Compute Timestamp from current time in Python.
|
||||
///
|
||||
/// timestamp = Timestamp()
|
||||
/// timestamp.GetCurrentTime()
|
||||
///
|
||||
/// # JSON Mapping
|
||||
///
|
||||
/// In JSON format, the Timestamp type is encoded as a string in the
|
||||
/// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
|
||||
/// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
|
||||
/// where {year} is always expressed using four digits while {month}, {day},
|
||||
/// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
|
||||
/// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
|
||||
/// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
|
||||
/// is required. A proto3 JSON serializer should always use UTC (as indicated by
|
||||
/// "Z") when printing the Timestamp type and a proto3 JSON parser should be
|
||||
/// able to accept both UTC and other timezones (as indicated by an offset).
|
||||
///
|
||||
/// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
|
||||
/// 01:30 UTC on January 15, 2017.
|
||||
///
|
||||
/// In JavaScript, one can convert a Date object to this format using the
|
||||
/// standard
|
||||
/// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
|
||||
/// method. In Python, a standard `datetime.datetime` object can be converted
|
||||
/// to this format using
|
||||
/// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
|
||||
/// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
|
||||
/// the Joda Time's [`ISODateTimeFormat.dateTime()`](
|
||||
/// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
|
||||
/// ) to obtain a formatter capable of generating timestamps in this format.
|
||||
/// </summary>
|
||||
public sealed partial class Timestamp : pb::IMessage<Timestamp>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Timestamp> _parser = new pb::MessageParser<Timestamp>(() => new Timestamp());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pb::MessageParser<Timestamp> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::LC.Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Timestamp() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Timestamp(Timestamp other) : this() {
|
||||
seconds_ = other.seconds_;
|
||||
nanos_ = other.nanos_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public Timestamp Clone() {
|
||||
return new Timestamp(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "seconds" field.</summary>
|
||||
public const int SecondsFieldNumber = 1;
|
||||
private long seconds_;
|
||||
/// <summary>
|
||||
/// Represents seconds of UTC time since Unix epoch
|
||||
/// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
/// 9999-12-31T23:59:59Z inclusive.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public long Seconds {
|
||||
get { return seconds_; }
|
||||
set {
|
||||
seconds_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "nanos" field.</summary>
|
||||
public const int NanosFieldNumber = 2;
|
||||
private int nanos_;
|
||||
/// <summary>
|
||||
/// Non-negative fractions of a second at nanosecond resolution. Negative
|
||||
/// second values with fractions must still have non-negative nanos values
|
||||
/// that count forward in time. Must be from 0 to 999,999,999
|
||||
/// inclusive.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int Nanos {
|
||||
get { return nanos_; }
|
||||
set {
|
||||
nanos_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Timestamp);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public bool Equals(Timestamp other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Seconds != other.Seconds) return false;
|
||||
if (Nanos != other.Nanos) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Seconds != 0L) hash ^= Seconds.GetHashCode();
|
||||
if (Nanos != 0) hash ^= Nanos.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Seconds != 0L) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt64(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteInt32(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Seconds != 0L) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt64(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteInt32(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Seconds != 0L) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt64Size(Seconds);
|
||||
}
|
||||
if (Nanos != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Nanos);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(Timestamp other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Seconds != 0L) {
|
||||
Seconds = other.Seconds;
|
||||
}
|
||||
if (other.Nanos != 0) {
|
||||
Nanos = other.Nanos;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
Seconds = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Nanos = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
Seconds = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Nanos = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion Designer generated code
|
|
@ -0,0 +1,344 @@
|
|||
#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
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
public partial class Timestamp : ICustomDiagnosticMessage, IComparable<Timestamp>
|
||||
{
|
||||
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
// Constants determined programmatically, but then hard-coded so they can be constant expressions.
|
||||
private const long BclSecondsAtUnixEpoch = 62135596800;
|
||||
internal const long UnixSecondsAtBclMaxValue = 253402300799;
|
||||
internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch;
|
||||
internal const int MaxNanos = Duration.NanosecondsPerSecond - 1;
|
||||
|
||||
private static bool IsNormalized(long seconds, int nanoseconds) =>
|
||||
nanoseconds >= 0 &&
|
||||
nanoseconds <= MaxNanos &&
|
||||
seconds >= UnixSecondsAtBclMinValue &&
|
||||
seconds <= UnixSecondsAtBclMaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the difference between one <see cref="Timestamp"/> and another, as a <see cref="Duration"/>.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The timestamp to subtract from. Must not be null.</param>
|
||||
/// <param name="rhs">The timestamp to subtract. Must not be null.</param>
|
||||
/// <returns>The difference between the two specified timestamps.</returns>
|
||||
public static Duration operator -(Timestamp lhs, Timestamp rhs)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(lhs, "lhs");
|
||||
ProtoPreconditions.CheckNotNull(rhs, "rhs");
|
||||
checked
|
||||
{
|
||||
return Duration.Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Duration"/> to a <see cref="Timestamp"/>, to obtain another <c>Timestamp</c>.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The timestamp to add the duration to. Must not be null.</param>
|
||||
/// <param name="rhs">The duration to add. Must not be null.</param>
|
||||
/// <returns>The result of adding the duration to the timestamp.</returns>
|
||||
public static Timestamp operator +(Timestamp lhs, Duration rhs)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(lhs, "lhs");
|
||||
ProtoPreconditions.CheckNotNull(rhs, "rhs");
|
||||
checked
|
||||
{
|
||||
return Normalize(lhs.Seconds + rhs.Seconds, lhs.Nanos + rhs.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a <see cref="Duration"/> from a <see cref="Timestamp"/>, to obtain another <c>Timestamp</c>.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The timestamp to subtract the duration from. Must not be null.</param>
|
||||
/// <param name="rhs">The duration to subtract.</param>
|
||||
/// <returns>The result of subtracting the duration from the timestamp.</returns>
|
||||
public static Timestamp operator -(Timestamp lhs, Duration rhs)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(lhs, "lhs");
|
||||
ProtoPreconditions.CheckNotNull(rhs, "rhs");
|
||||
checked
|
||||
{
|
||||
return Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this timestamp into a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resulting <c>DateTime</c> will always have a <c>Kind</c> of <c>Utc</c>.
|
||||
/// If the timestamp is not a precise number of ticks, it will be truncated towards the start
|
||||
/// of time. For example, a timestamp with a <see cref="Nanos"/> value of 99 will result in a
|
||||
/// <see cref="DateTime"/> value precisely on a second.
|
||||
/// </remarks>
|
||||
/// <returns>This timestamp as a <c>DateTime</c>.</returns>
|
||||
/// <exception cref="InvalidOperationException">The timestamp contains invalid values; either it is
|
||||
/// incorrectly normalized or is outside the valid range.</exception>
|
||||
public DateTime ToDateTime()
|
||||
{
|
||||
if (!IsNormalized(Seconds, Nanos))
|
||||
{
|
||||
throw new InvalidOperationException(@"Timestamp contains invalid values: Seconds={Seconds}; Nanos={Nanos}");
|
||||
}
|
||||
return UnixEpoch.AddSeconds(Seconds).AddTicks(Nanos / Duration.NanosecondsPerTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this timestamp into a <see cref="DateTimeOffset"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resulting <c>DateTimeOffset</c> will always have an <c>Offset</c> of zero.
|
||||
/// If the timestamp is not a precise number of ticks, it will be truncated towards the start
|
||||
/// of time. For example, a timestamp with a <see cref="Nanos"/> value of 99 will result in a
|
||||
/// <see cref="DateTimeOffset"/> value precisely on a second.
|
||||
/// </remarks>
|
||||
/// <returns>This timestamp as a <c>DateTimeOffset</c>.</returns>
|
||||
/// <exception cref="InvalidOperationException">The timestamp contains invalid values; either it is
|
||||
/// incorrectly normalized or is outside the valid range.</exception>
|
||||
public DateTimeOffset ToDateTimeOffset()
|
||||
{
|
||||
return new DateTimeOffset(ToDateTime(), TimeSpan.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <see cref="DateTime"/> to a <see cref="Timestamp"/>.
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <exception cref="ArgumentException">The <c>Kind</c> of <paramref name="dateTime"/> is not <c>DateTimeKind.Utc</c>.</exception>
|
||||
/// <returns>The converted timestamp.</returns>
|
||||
public static Timestamp FromDateTime(DateTime dateTime)
|
||||
{
|
||||
if (dateTime.Kind != DateTimeKind.Utc)
|
||||
{
|
||||
throw new ArgumentException("Conversion from DateTime to Timestamp requires the DateTime kind to be Utc", "dateTime");
|
||||
}
|
||||
// Do the arithmetic using DateTime.Ticks, which is always non-negative, making things simpler.
|
||||
long secondsSinceBclEpoch = dateTime.Ticks / TimeSpan.TicksPerSecond;
|
||||
int nanoseconds = (int) (dateTime.Ticks % TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick;
|
||||
return new Timestamp { Seconds = secondsSinceBclEpoch - BclSecondsAtUnixEpoch, Nanos = nanoseconds };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="DateTimeOffset"/> to a <see cref="Timestamp"/>
|
||||
/// </summary>
|
||||
/// <remarks>The offset is taken into consideration when converting the value (so the same instant in time
|
||||
/// is represented) but is not a separate part of the resulting value. In other words, there is no
|
||||
/// roundtrip operation to retrieve the original <c>DateTimeOffset</c>.</remarks>
|
||||
/// <param name="dateTimeOffset">The date and time (with UTC offset) to convert to a timestamp.</param>
|
||||
/// <returns>The converted timestamp.</returns>
|
||||
public static Timestamp FromDateTimeOffset(DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
// We don't need to worry about this having negative ticks: DateTimeOffset is constrained to handle
|
||||
// values whose *UTC* value is in the range of DateTime.
|
||||
return FromDateTime(dateTimeOffset.UtcDateTime);
|
||||
}
|
||||
|
||||
internal static Timestamp Normalize(long seconds, int nanoseconds)
|
||||
{
|
||||
int extraSeconds = nanoseconds / Duration.NanosecondsPerSecond;
|
||||
seconds += extraSeconds;
|
||||
nanoseconds -= extraSeconds * Duration.NanosecondsPerSecond;
|
||||
|
||||
if (nanoseconds < 0)
|
||||
{
|
||||
nanoseconds += Duration.NanosecondsPerSecond;
|
||||
seconds--;
|
||||
}
|
||||
return new Timestamp { Seconds = seconds, Nanos = nanoseconds };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a timestamp specified in seconds/nanoseconds to a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the value is a normalized duration in the range described in <c>timestamp.proto</c>,
|
||||
/// <paramref name="diagnosticOnly"/> is ignored. Otherwise, if the parameter is <c>true</c>,
|
||||
/// a JSON object with a warning is returned; if it is <c>false</c>, an <see cref="InvalidOperationException"/> is thrown.
|
||||
/// </remarks>
|
||||
/// <param name="seconds">Seconds portion of the duration.</param>
|
||||
/// <param name="nanoseconds">Nanoseconds portion of the duration.</param>
|
||||
/// <param name="diagnosticOnly">Determines the handling of non-normalized values</param>
|
||||
/// <exception cref="InvalidOperationException">The represented duration is invalid, and <paramref name="diagnosticOnly"/> is <c>false</c>.</exception>
|
||||
internal static string ToJson(long seconds, int nanoseconds, bool diagnosticOnly)
|
||||
{
|
||||
if (IsNormalized(seconds, nanoseconds))
|
||||
{
|
||||
// Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
|
||||
DateTime dateTime = UnixEpoch.AddSeconds(seconds);
|
||||
var builder = new StringBuilder();
|
||||
builder.Append('"');
|
||||
builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", CultureInfo.InvariantCulture));
|
||||
Duration.AppendNanoseconds(builder, nanoseconds);
|
||||
builder.Append("Z\"");
|
||||
return builder.ToString();
|
||||
}
|
||||
if (diagnosticOnly)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
"{{ \"@warning\": \"Invalid Timestamp\", \"seconds\": \"{0}\", \"nanos\": {1} }}",
|
||||
seconds,
|
||||
nanoseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Non-normalized timestamp value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given another timestamp, returns 0 if the timestamps are equivalent, -1 if this timestamp precedes the other, and 1 otherwise
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="other">Timestamp to compare</param>
|
||||
/// <returns>an integer indicating whether this timestamp precedes or follows the other</returns>
|
||||
public int CompareTo(Timestamp other)
|
||||
{
|
||||
return other == null ? 1
|
||||
: Seconds < other.Seconds ? -1
|
||||
: Seconds > other.Seconds ? 1
|
||||
: Nanos < other.Nanos ? -1
|
||||
: Nanos > other.Nanos ? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two timestamps and returns whether the first is less than (chronologically precedes) the second
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if a precedes b</returns>
|
||||
public static bool operator <(Timestamp a, Timestamp b)
|
||||
{
|
||||
return a.CompareTo(b) < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two timestamps and returns whether the first is greater than (chronologically follows) the second
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if a follows b</returns>
|
||||
public static bool operator >(Timestamp a, Timestamp b)
|
||||
{
|
||||
return a.CompareTo(b) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two timestamps and returns whether the first is less than (chronologically precedes) the second
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if a precedes b</returns>
|
||||
public static bool operator <=(Timestamp a, Timestamp b)
|
||||
{
|
||||
return a.CompareTo(b) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two timestamps and returns whether the first is greater than (chronologically follows) the second
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if a follows b</returns>
|
||||
public static bool operator >=(Timestamp a, Timestamp b)
|
||||
{
|
||||
return a.CompareTo(b) >= 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether two timestamps are equivalent
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if the two timestamps refer to the same nanosecond</returns>
|
||||
public static bool operator ==(Timestamp a, Timestamp b)
|
||||
{
|
||||
return ReferenceEquals(a, b) || (ReferenceEquals(a, null) ? (ReferenceEquals(b, null) ? true : false) : a.Equals(b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether two timestamps differ
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure the timestamps are normalized. Comparing non-normalized timestamps is not specified and may give unexpected results.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns>true if the two timestamps differ</returns>
|
||||
public static bool operator !=(Timestamp a, Timestamp b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this <see cref="Timestamp"/> for diagnostic purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normally the returned value will be a JSON string value (including leading and trailing quotes) but
|
||||
/// when the value is non-normalized or out of range, a JSON object representation will be returned
|
||||
/// instead, including a warning. This is to avoid exceptions being thrown when trying to
|
||||
/// diagnose problems - the regular JSON formatter will still throw an exception for non-normalized
|
||||
/// values.
|
||||
/// </remarks>
|
||||
/// <returns>A string representation of this value.</returns>
|
||||
public string ToDiagnosticString()
|
||||
{
|
||||
return ToJson(Seconds, Nanos, true);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,99 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
public partial class Value
|
||||
{
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with a string value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to set for the StringValue property.</param>
|
||||
/// <returns>A newly-created Value message with the given value.</returns>
|
||||
public static Value ForString(string value)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(value, "value");
|
||||
return new Value { StringValue = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with a number value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to set for the NumberValue property.</param>
|
||||
/// <returns>A newly-created Value message with the given value.</returns>
|
||||
public static Value ForNumber(double value)
|
||||
{
|
||||
return new Value { NumberValue = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with a Boolean value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to set for the BoolValue property.</param>
|
||||
/// <returns>A newly-created Value message with the given value.</returns>
|
||||
public static Value ForBool(bool value)
|
||||
{
|
||||
return new Value { BoolValue = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with a null initial value.
|
||||
/// </summary>
|
||||
/// <returns>A newly-created Value message a null initial value.</returns>
|
||||
public static Value ForNull()
|
||||
{
|
||||
return new Value { NullValue = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with an initial list of values.
|
||||
/// </summary>
|
||||
/// <remarks>The values provided are not cloned; the references are copied directly.</remarks>
|
||||
/// <returns>A newly-created Value message an initial list value.</returns>
|
||||
public static Value ForList(params Value[] values)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(values, "values");
|
||||
return new Value { ListValue = new ListValue { Values = { values } } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to create a Value message with an initial struct value
|
||||
/// </summary>
|
||||
/// <remarks>The value provided is not cloned; the reference is copied directly.</remarks>
|
||||
/// <returns>A newly-created Value message an initial struct value.</returns>
|
||||
public static Value ForStruct(Struct value)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(value, "value");
|
||||
return new Value { StructValue = value };
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,42 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf.WellKnownTypes
|
||||
{
|
||||
public static partial class WrappersReflection
|
||||
{
|
||||
/// <summary>
|
||||
/// Field number for the single "value" field in all wrapper types.
|
||||
/// </summary>
|
||||
internal const int WrapperValueFieldNumber = Int32Value.ValueFieldNumber;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used internally by the Protocol Buffer Library and generated
|
||||
/// message implementations. It is public only for the sake of those generated
|
||||
/// messages. Others should not use this class directly.
|
||||
/// <para>
|
||||
/// This class contains constants and helper functions useful for dealing with
|
||||
/// the Protocol Buffer wire format.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class WireFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Wire types within protobuf encoding.
|
||||
/// </summary>
|
||||
public enum WireType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Variable-length integer.
|
||||
/// </summary>
|
||||
Varint = 0,
|
||||
/// <summary>
|
||||
/// A fixed-length 64-bit value.
|
||||
/// </summary>
|
||||
Fixed64 = 1,
|
||||
/// <summary>
|
||||
/// A length-delimited value, i.e. a length followed by that many bytes of data.
|
||||
/// </summary>
|
||||
LengthDelimited = 2,
|
||||
/// <summary>
|
||||
/// A "start group" value
|
||||
/// </summary>
|
||||
StartGroup = 3,
|
||||
/// <summary>
|
||||
/// An "end group" value
|
||||
/// </summary>
|
||||
EndGroup = 4,
|
||||
/// <summary>
|
||||
/// A fixed-length 32-bit value.
|
||||
/// </summary>
|
||||
Fixed32 = 5
|
||||
}
|
||||
|
||||
private const int TagTypeBits = 3;
|
||||
private const uint TagTypeMask = (1 << TagTypeBits) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Given a tag value, determines the wire type (lower 3 bits).
|
||||
/// </summary>
|
||||
public static WireType GetTagWireType(uint tag)
|
||||
{
|
||||
return (WireType) (tag & TagTypeMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a tag value, determines the field number (the upper 29 bits).
|
||||
/// </summary>
|
||||
public static int GetTagFieldNumber(uint tag)
|
||||
{
|
||||
return (int) tag >> TagTypeBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a tag value given a field number and wire type.
|
||||
/// </summary>
|
||||
public static uint MakeTag(int fieldNumber, WireType wireType)
|
||||
{
|
||||
return (uint) (fieldNumber << TagTypeBits) | (uint) wireType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for writing to a steam / IBufferWriter
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
internal struct WriteBufferHelper
|
||||
{
|
||||
private IBufferWriter<byte> bufferWriter;
|
||||
private CodedOutputStream codedOutputStream;
|
||||
|
||||
public CodedOutputStream CodedOutputStream => codedOutputStream;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance with a coded output stream.
|
||||
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
|
||||
/// and we can write directly into it without copying.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Initialize(CodedOutputStream codedOutputStream, out WriteBufferHelper instance)
|
||||
{
|
||||
instance.bufferWriter = null;
|
||||
instance.codedOutputStream = codedOutputStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance with a buffer writer.
|
||||
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
|
||||
/// and we can write directly into it without copying.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer)
|
||||
{
|
||||
instance.bufferWriter = bufferWriter;
|
||||
instance.codedOutputStream = null;
|
||||
buffer = default; // TODO: initialize the initial buffer so that the first write is not via slowpath.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance with a buffer represented by a single span (i.e. buffer cannot be refreshed)
|
||||
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
|
||||
/// and we can write directly into it without copying.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void InitializeNonRefreshable(out WriteBufferHelper instance)
|
||||
{
|
||||
instance.bufferWriter = null;
|
||||
instance.codedOutputStream = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that SpaceLeft returns zero.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CheckNoSpaceLeft(ref WriterInternalState state)
|
||||
{
|
||||
if (GetSpaceLeft(ref state) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Did not write as much data as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If writing to a flat array, returns the space left in the array. Otherwise,
|
||||
/// throws an InvalidOperationException.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetSpaceLeft(ref WriterInternalState state)
|
||||
{
|
||||
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream == null && state.writeBufferHelper.bufferWriter == null)
|
||||
{
|
||||
return state.limit - state.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"SpaceLeft can only be called on CodedOutputStreams that are " +
|
||||
"writing to a flat array or when writing to a single span.");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state)
|
||||
{
|
||||
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
|
||||
{
|
||||
// because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
|
||||
state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
|
||||
// reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer.
|
||||
state.position = 0;
|
||||
}
|
||||
else if (state.writeBufferHelper.bufferWriter != null)
|
||||
{
|
||||
// commit the bytes and get a new buffer to write to.
|
||||
state.writeBufferHelper.bufferWriter.Advance(state.position);
|
||||
state.position = 0;
|
||||
buffer = state.writeBufferHelper.bufferWriter.GetSpan();
|
||||
state.limit = buffer.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're writing to a single buffer.
|
||||
throw new CodedOutputStream.OutOfSpaceException();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Flush(ref Span<byte> buffer, ref WriterInternalState state)
|
||||
{
|
||||
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
|
||||
{
|
||||
// because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
|
||||
state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
|
||||
state.position = 0;
|
||||
}
|
||||
else if (state.writeBufferHelper.bufferWriter != null)
|
||||
{
|
||||
// calling Advance invalidates the current buffer and we must not continue writing to it,
|
||||
// so we set the current buffer to point to an empty Span. If any subsequent writes happen,
|
||||
// the first subsequent write will trigger refresing of the buffer.
|
||||
state.writeBufferHelper.bufferWriter.Advance(state.position);
|
||||
state.position = 0;
|
||||
state.limit = 0;
|
||||
buffer = default; // invalidate the current buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
/// <summary>
|
||||
/// An opaque struct that represents the current serialization state and is passed along
|
||||
/// as the serialization proceeds.
|
||||
/// All the public methods are intended to be invoked only by the generated code,
|
||||
/// users should never invoke them directly.
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public ref struct WriteContext
|
||||
{
|
||||
internal Span<byte> buffer;
|
||||
internal WriterInternalState state;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(ref Span<byte> buffer, ref WriterInternalState state, out WriteContext ctx)
|
||||
{
|
||||
ctx.buffer = buffer;
|
||||
ctx.state = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a WriteContext instance from CodedOutputStream.
|
||||
/// WARNING: internally this copies the CodedOutputStream's state, so after done with the WriteContext,
|
||||
/// the CodedOutputStream's state needs to be updated.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(CodedOutputStream output, out WriteContext ctx)
|
||||
{
|
||||
ctx.buffer = new Span<byte>(output.InternalBuffer);
|
||||
// ideally we would use a reference to the original state, but that doesn't seem possible
|
||||
// so we just copy the struct that holds the state. We will need to later store the state back
|
||||
// into CodedOutputStream if we want to keep it usable.
|
||||
ctx.state = output.InternalState;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(IBufferWriter<byte> output, out WriteContext ctx)
|
||||
{
|
||||
ctx.buffer = default;
|
||||
ctx.state = default;
|
||||
WriteBufferHelper.Initialize(output, out ctx.state.writeBufferHelper, out ctx.buffer);
|
||||
ctx.state.limit = ctx.buffer.Length;
|
||||
ctx.state.position = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Initialize(ref Span<byte> buffer, out WriteContext ctx)
|
||||
{
|
||||
ctx.buffer = buffer;
|
||||
ctx.state = default;
|
||||
ctx.state.limit = ctx.buffer.Length;
|
||||
ctx.state.position = 0;
|
||||
WriteBufferHelper.InitializeNonRefreshable(out ctx.state.writeBufferHelper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a double field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteDouble(double value)
|
||||
{
|
||||
WritingPrimitives.WriteDouble(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a float field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFloat(float value)
|
||||
{
|
||||
WritingPrimitives.WriteFloat(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a uint64 field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteUInt64(ulong value)
|
||||
{
|
||||
WritingPrimitives.WriteUInt64(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int64 field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteInt64(long value)
|
||||
{
|
||||
WritingPrimitives.WriteInt64(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an int32 field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteInt32(int value)
|
||||
{
|
||||
WritingPrimitives.WriteInt32(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a fixed64 field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFixed64(ulong value)
|
||||
{
|
||||
WritingPrimitives.WriteFixed64(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a fixed32 field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteFixed32(uint value)
|
||||
{
|
||||
WritingPrimitives.WriteFixed32(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a bool field value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteBool(bool value)
|
||||
{
|
||||
WritingPrimitives.WriteBool(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string field value, without a tag.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteString(string value)
|
||||
{
|
||||
WritingPrimitives.WriteString(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message, without a tag.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteMessage(IMessage value)
|
||||
{
|
||||
WritingPrimitivesMessages.WriteMessage(ref this, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a group, without a tag, to the stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteGroup(IMessage value)
|
||||
{
|
||||
WritingPrimitivesMessages.WriteGroup(ref this, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a byte string, without a tag, to the stream.
|
||||
/// The data is length-prefixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteBytes(ByteString value)
|
||||
{
|
||||
WritingPrimitives.WriteBytes(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a uint32 value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
WritingPrimitives.WriteUInt32(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an enum value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteEnum(int value)
|
||||
{
|
||||
WritingPrimitives.WriteEnum(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sfixed32 value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public void WriteSFixed32(int value)
|
||||
{
|
||||
WritingPrimitives.WriteSFixed32(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sfixed64 value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSFixed64(long value)
|
||||
{
|
||||
WritingPrimitives.WriteSFixed64(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sint32 value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSInt32(int value)
|
||||
{
|
||||
WritingPrimitives.WriteSInt32(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an sint64 value, without a tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
public void WriteSInt64(long value)
|
||||
{
|
||||
WritingPrimitives.WriteSInt64(ref buffer, ref state, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a length (in bytes) for length-delimited data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method simply writes a rawint, but exists for clarity in calling code.
|
||||
/// </remarks>
|
||||
/// <param name="length">Length value, in bytes.</param>
|
||||
public void WriteLength(int length)
|
||||
{
|
||||
WritingPrimitives.WriteLength(ref buffer, ref state, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes and writes a tag.
|
||||
/// </summary>
|
||||
/// <param name="fieldNumber">The number of the field to write the tag for</param>
|
||||
/// <param name="type">The wire format type of the tag to write</param>
|
||||
public void WriteTag(int fieldNumber, WireFormat.WireType type)
|
||||
{
|
||||
WritingPrimitives.WriteTag(ref buffer, ref state, fieldNumber, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an already-encoded tag.
|
||||
/// </summary>
|
||||
/// <param name="tag">The encoded tag</param>
|
||||
public void WriteTag(uint tag)
|
||||
{
|
||||
WritingPrimitives.WriteTag(ref buffer, ref state, tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given single-byte tag.
|
||||
/// </summary>
|
||||
/// <param name="b1">The encoded tag</param>
|
||||
public void WriteRawTag(byte b1)
|
||||
{
|
||||
WritingPrimitives.WriteRawTag(ref buffer, ref state, b1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given two-byte tag.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2)
|
||||
{
|
||||
WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given three-byte tag.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3)
|
||||
{
|
||||
WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given four-byte tag.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
/// <param name="b4">The fourth byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4)
|
||||
{
|
||||
WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given five-byte tag.
|
||||
/// </summary>
|
||||
/// <param name="b1">The first byte of the encoded tag</param>
|
||||
/// <param name="b2">The second byte of the encoded tag</param>
|
||||
/// <param name="b3">The third byte of the encoded tag</param>
|
||||
/// <param name="b4">The fourth byte of the encoded tag</param>
|
||||
/// <param name="b5">The fifth byte of the encoded tag</param>
|
||||
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5)
|
||||
{
|
||||
WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4, b5);
|
||||
}
|
||||
|
||||
internal void Flush()
|
||||
{
|
||||
WriteBufferHelper.Flush(ref buffer, ref state);
|
||||
}
|
||||
|
||||
internal void CheckNoSpaceLeft()
|
||||
{
|
||||
WriteBufferHelper.CheckNoSpaceLeft(ref state);
|
||||
}
|
||||
|
||||
internal void CopyStateTo(CodedOutputStream output)
|
||||
{
|
||||
output.InternalState = state;
|
||||
}
|
||||
|
||||
internal void LoadStateFrom(CodedOutputStream output)
|
||||
{
|
||||
state = output.InternalState;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using LC.Google.Protobuf.Collections;
|
||||
|
||||
namespace LC.Google.Protobuf
|
||||
{
|
||||
|
||||
// warning: this is a mutable struct, so it needs to be only passed as a ref!
|
||||
internal struct WriterInternalState
|
||||
{
|
||||
// NOTE: the Span representing the current buffer is kept separate so that this doesn't have to be a ref struct and so it can
|
||||
// be included in CodedOutputStream's internal state
|
||||
|
||||
internal int limit; // the size of the current buffer
|
||||
internal int position; // position in the current buffer
|
||||
|
||||
internal WriteBufferHelper writeBufferHelper;
|
||||
|
||||
// If non-null, the top level parse method was started with given coded output stream as an argument
|
||||
// which also means we can potentially fallback to calling WriteTo(CodedOutputStream cos) if needed.
|
||||
internal CodedOutputStream CodedOutputStream => writeBufferHelper.CodedOutputStream;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue