| #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 System; | |
| using System.Collections.Concurrent; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using static Google.Protobuf.Reflection.FeatureSet.Types; | |
| namespace Google.Protobuf.Reflection; | |
| /// <summary> | |
| /// A resolved set of features for a file, message etc. | |
| /// </summary> | |
| /// <remarks> | |
| /// Only features supported by the C# runtime are exposed; currently | |
| /// all enums in C# are open, and we never perform UTF-8 validation. | |
| /// If either of those features are ever implemented in this runtime, | |
| /// the feature settings will be exposed as properties in this class. | |
| /// </remarks> | |
| internal sealed partial class FeatureSetDescriptor | |
| { | |
| private static readonly ConcurrentDictionary<FeatureSet, FeatureSetDescriptor> cache = new(); | |
| private static readonly IReadOnlyDictionary<Edition, FeatureSetDescriptor> descriptorsByEdition = BuildEditionDefaults(); | |
| // Note: if the debugger is set to break within this code, various type initializers will fail | |
| // as the debugger will try to call ToString() on messages, requiring descriptors to be accessed etc. | |
| // There's a possible workaround of using a hard-coded bootstrapping FeatureSetDescriptor to be returned | |
| // by GetEditionDefaults if descriptorsByEdition is null, but it's ugly and likely just pushes the problem | |
| // elsewhere. Normal debugging sessions (where the initial bootstrapping code doesn't hit any breakpoints) | |
| // do not cause any problems. | |
| private static IReadOnlyDictionary<Edition, FeatureSetDescriptor> BuildEditionDefaults() | |
| { | |
| var featureSetDefaults = FeatureSetDefaults.Parser.ParseFrom(Convert.FromBase64String(DefaultsBase64)); | |
| var ret = new Dictionary<Edition, FeatureSetDescriptor>(); | |
| // Note: Enum.GetValues<TEnum> isn't available until .NET 5. It's not worth making this conditional | |
| // based on that. | |
| var supportedEditions = ((Edition[]) Enum.GetValues(typeof(Edition))) | |
| .OrderBy(x => x) | |
| .Where(e => e >= featureSetDefaults.MinimumEdition && (e <= featureSetDefaults.MaximumEdition || e == Edition.Unstable)); | |
| // We assume the embedded defaults will always contain "legacy". | |
| var currentDescriptor = MaybeCreateDescriptor(Edition.Legacy); | |
| foreach (var edition in supportedEditions) | |
| { | |
| currentDescriptor = MaybeCreateDescriptor(edition) ?? currentDescriptor; | |
| ret[edition] = currentDescriptor; | |
| } | |
| return ret; | |
| FeatureSetDescriptor MaybeCreateDescriptor(Edition edition) | |
| { | |
| var editionDefaults = featureSetDefaults.Defaults.SingleOrDefault(d => d.Edition == edition); | |
| if (editionDefaults is null) | |
| { | |
| return null; | |
| } | |
| var proto = new FeatureSet(); | |
| proto.MergeFrom(editionDefaults.FixedFeatures); | |
| proto.MergeFrom(editionDefaults.OverridableFeatures); | |
| return new FeatureSetDescriptor(proto); | |
| } | |
| } | |
| internal static FeatureSetDescriptor GetEditionDefaults(Edition edition) => | |
| descriptorsByEdition.TryGetValue(edition, out var defaults) ? defaults | |
| : throw new ArgumentOutOfRangeException($"Unsupported edition: {edition}"); | |
| // Visible for testing. The underlying feature set proto, usually derived during | |
| // feature resolution. | |
| internal FeatureSet Proto { get; } | |
| /// <summary> | |
| /// Only relevant to fields. Indicates if a field has explicit presence. | |
| /// </summary> | |
| internal FieldPresence FieldPresence => Proto.FieldPresence; | |
| /// <summary> | |
| /// Only relevant to fields. Indicates how a repeated field should be encoded. | |
| /// </summary> | |
| internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; | |
| /// <summary> | |
| /// Only relevant to fields. Indicates how a message-valued field should be encoded. | |
| /// </summary> | |
| internal MessageEncoding MessageEncoding => Proto.MessageEncoding; | |
| private FeatureSetDescriptor(FeatureSet proto) | |
| { | |
| Proto = proto; | |
| } | |
| /// <summary> | |
| /// Returns a new descriptor based on this one, with the specified overrides. | |
| /// Multiple calls to this method that produce equivalent feature sets will return | |
| /// the same instance. | |
| /// </summary> | |
| /// <param name="overrides">The proto representation of the "child" feature set to merge with this | |
| /// one. May be null, in which case this descriptor is returned.</param> | |
| /// <returns>A descriptor based on the current one, with the given set of overrides.</returns> | |
| public FeatureSetDescriptor MergedWith(FeatureSet overrides) | |
| { | |
| if (overrides is null) | |
| { | |
| return this; | |
| } | |
| // Note: It would be nice if we could avoid cloning unless | |
| // there are actual changes, but this won't happen that often; | |
| // it'll be temporary garbage. | |
| var clone = Proto.Clone(); | |
| clone.MergeFrom(overrides); | |
| return cache.GetOrAdd(clone, clone => new FeatureSetDescriptor(clone)); | |
| } | |
| } |