378 lines
14 KiB
C#
378 lines
14 KiB
C#
|
#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());
|
|||
|
}
|
|||
|
}
|
|||
|
}
|