#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; /// /// A resolved set of features for a file, message etc. /// /// /// 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. /// internal sealed partial class FeatureSetDescriptor { private static readonly ConcurrentDictionary cache = new(); private static readonly IReadOnlyDictionary 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 BuildEditionDefaults() { var featureSetDefaults = FeatureSetDefaults.Parser.ParseFrom(Convert.FromBase64String(DefaultsBase64)); var ret = new Dictionary(); // Note: Enum.GetValues 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); // 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; } /// /// Only relevant to fields. Indicates if a field has explicit presence. /// internal FieldPresence FieldPresence => Proto.FieldPresence; /// /// Only relevant to fields. Indicates how a repeated field should be encoded. /// internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; /// /// Only relevant to fields. Indicates how a message-valued field should be encoded. /// internal MessageEncoding MessageEncoding => Proto.MessageEncoding; private FeatureSetDescriptor(FeatureSet proto) { Proto = proto; } /// /// 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. /// /// The proto representation of the "child" feature set to merge with this /// one. May be null, in which case this descriptor is returned. /// A descriptor based on the current one, with the given set of overrides. 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)); } }