|  | #region Copyright notice and license | 
|  | // Protocol Buffers - Google's data interchange format | 
|  | // Copyright 2008 Google Inc.  All rights reserved. | 
|  | // | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://developers.google.com/open-source/licenses/bsd | 
|  | #endregion | 
|  |  | 
|  | using Google.Protobuf.Collections; | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Linq; | 
|  | using System.Reflection; | 
|  | using System.Security; | 
|  |  | 
|  | namespace Google.Protobuf | 
|  | { | 
|  | /// <summary> | 
|  | /// Methods for managing <see cref="ExtensionSet{TTarget}"/>s with null checking. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | /// Most users will not use this class directly. Accessing extensions for a given message | 
|  | /// is usually performed by obtaining the generated instance of | 
|  | /// <see cref="Extension{TTarget,TValue}"/> or <see cref="RepeatedExtension{TTarget,TValue}"/> | 
|  | /// and then calling methods on <see cref="IExtendableMessage{T}"/> (which is implemented by | 
|  | /// all generated messages). | 
|  | /// </remarks> | 
|  | 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> | 
|  | { | 
|  | if (TryGetValue(ref set, extension, out IExtensionValue value)) | 
|  | { | 
|  | // The stored ExtensionValue can be a different type to what is being requested. | 
|  | // This happens when the same extension proto is compiled in different assemblies. | 
|  | // To allow consuming assemblies to still get the value when the TValue type is | 
|  | // different, this get method: | 
|  | // 1. Attempts to cast the value to the expected ExtensionValue<TValue>. | 
|  | //    This is the usual case. It is used first because it avoids possibly boxing the value. | 
|  | // 2. Fallback to get the value as object from IExtensionValue then casting. | 
|  | //    This allows for someone to specify a TValue of object. They can then convert | 
|  | //    the values to bytes and reparse using expected value. | 
|  | // 3. If neither of these work, throw a user friendly error that the types aren't compatible. | 
|  | if (value is ExtensionValue<TValue> extensionValue) | 
|  | { | 
|  | return extensionValue.GetValue(); | 
|  | } | 
|  | else if (value.GetValue() is TValue underlyingValue) | 
|  | { | 
|  | return underlyingValue; | 
|  | } | 
|  | else | 
|  | { | 
|  | var valueType = value.GetType().GetTypeInfo(); | 
|  | if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(ExtensionValue<>)) | 
|  | { | 
|  | var storedType = valueType.GenericTypeArguments[0]; | 
|  | throw new InvalidOperationException( | 
|  | "The stored extension value has a type of '" + storedType.AssemblyQualifiedName + "'. " + | 
|  | "This a different from the requested type of '" + typeof(TValue).AssemblyQualifiedName + "'."); | 
|  | } | 
|  | else | 
|  | { | 
|  | throw new InvalidOperationException("Unexpected extension value type: " + valueType.AssemblyQualifiedName); | 
|  | } | 
|  | } | 
|  | } | 
|  | 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> | 
|  | { | 
|  | if (TryGetValue(ref set, extension, out IExtensionValue value)) | 
|  | { | 
|  | if (value is RepeatedExtensionValue<TValue> extensionValue) | 
|  | { | 
|  | return extensionValue.GetValue(); | 
|  | } | 
|  | else | 
|  | { | 
|  | var valueType = value.GetType().GetTypeInfo(); | 
|  | if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>)) | 
|  | { | 
|  | var storedType = valueType.GenericTypeArguments[0]; | 
|  | throw new InvalidOperationException( | 
|  | "The stored extension value has a type of '" + storedType.AssemblyQualifiedName + "'. " + | 
|  | "This a different from the requested type of '" + typeof(TValue).AssemblyQualifiedName + "'."); | 
|  | } | 
|  | else | 
|  | { | 
|  | throw new InvalidOperationException("Unexpected extension value type: " + valueType.AssemblyQualifiedName); | 
|  | } | 
|  | } | 
|  | } | 
|  | 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> | 
|  | { | 
|  | return TryGetValue(ref set, extension, out IExtensionValue _); | 
|  | } | 
|  |  | 
|  | /// <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> | 
|  | { | 
|  | int lastFieldNumber = WireFormat.GetTagFieldNumber(ctx.LastTag); | 
|  |  | 
|  | if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out IExtensionValue extensionValue)) | 
|  | { | 
|  | extensionValue.MergeFrom(ref ctx); | 
|  | return true; | 
|  | } | 
|  | else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out Extension extension)) | 
|  | { | 
|  | IExtensionValue value = extension.CreateValue(); | 
|  | value.MergeFrom(ref ctx); | 
|  | 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) | 
|  | { | 
|  | if (first.ValuesByNumber.TryGetValue(pair.Key, out IExtensionValue 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) | 
|  | { | 
|  | if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out IExtensionValue 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()); | 
|  | } | 
|  | } | 
|  | } |