• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using System;
11 using System.Collections.Concurrent;
12 using static Google.Protobuf.Reflection.FeatureSet.Types;
13 
14 namespace Google.Protobuf.Reflection;
15 
16 /// <summary>
17 /// A resolved set of features for a file, message etc.
18 /// </summary>
19 /// <remarks>
20 /// Only features supported by the C# runtime are exposed; currently
21 /// all enums in C# are open, and we never perform UTF-8 validation.
22 /// If either of those features are ever implemented in this runtime,
23 /// the feature settings will be exposed as properties in this class.
24 /// </remarks>
25 internal sealed class FeatureSetDescriptor
26 {
27     private static readonly ConcurrentDictionary<FeatureSet, FeatureSetDescriptor> cache = new();
28 
29     // Note: this approach is deliberately chosen to circumvent bootstrapping issues.
30     // This can still be tested using the binary representation.
31     // TODO: Generate this code (as a partial class) from the binary representation.
32     private static readonly FeatureSetDescriptor edition2023Defaults = new FeatureSetDescriptor(
33         new FeatureSet
34         {
35             EnumType = EnumType.Open,
36             FieldPresence = FieldPresence.Explicit,
37             JsonFormat = JsonFormat.Allow,
38             MessageEncoding = MessageEncoding.LengthPrefixed,
39             RepeatedFieldEncoding = RepeatedFieldEncoding.Packed,
40             Utf8Validation = Utf8Validation.Verify,
41         });
42     private static readonly FeatureSetDescriptor proto2Defaults = new FeatureSetDescriptor(
43         new FeatureSet
44         {
45             EnumType = EnumType.Closed,
46             FieldPresence = FieldPresence.Explicit,
47             JsonFormat = JsonFormat.LegacyBestEffort,
48             MessageEncoding = MessageEncoding.LengthPrefixed,
49             RepeatedFieldEncoding = RepeatedFieldEncoding.Expanded,
50             Utf8Validation = Utf8Validation.None,
51         });
52     private static readonly FeatureSetDescriptor proto3Defaults = new FeatureSetDescriptor(
53         new FeatureSet
54         {
55             EnumType = EnumType.Open,
56             FieldPresence = FieldPresence.Implicit,
57             JsonFormat = JsonFormat.Allow,
58             MessageEncoding = MessageEncoding.LengthPrefixed,
59             RepeatedFieldEncoding = RepeatedFieldEncoding.Packed,
60             Utf8Validation = Utf8Validation.Verify,
61         });
62 
63     internal static FeatureSetDescriptor GetEditionDefaults(Edition edition) =>
64         edition switch
65         {
66             Edition.Proto2 => proto2Defaults,
67             Edition.Proto3 => proto3Defaults,
68             Edition._2023 => edition2023Defaults,
69             _ => throw new ArgumentOutOfRangeException($"Unsupported edition: {edition}")
70         };
71 
72     // Visible for testing. The underlying feature set proto, usually derived during
73     // feature resolution.
74     internal FeatureSet Proto { get; }
75 
76     /// <summary>
77     /// Only relevant to fields. Indicates if a field has explicit presence.
78     /// </summary>
79     internal FieldPresence FieldPresence => Proto.FieldPresence;
80 
81     /// <summary>
82     /// Only relevant to fields. Indicates how a repeated field should be encoded.
83     /// </summary>
84     internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding;
85 
86     /// <summary>
87     /// Only relevant to fields. Indicates how a message-valued field should be encoded.
88     /// </summary>
89     internal MessageEncoding MessageEncoding => Proto.MessageEncoding;
90 
FeatureSetDescriptor(FeatureSet proto)91     private FeatureSetDescriptor(FeatureSet proto)
92     {
93         Proto = proto;
94     }
95 
96     /// <summary>
97     /// Returns a new descriptor based on this one, with the specified overrides.
98     /// Multiple calls to this method that produce equivalent feature sets will return
99     /// the same instance.
100     /// </summary>
101     /// <param name="overrides">The proto representation of the "child" feature set to merge with this
102     /// one. May be null, in which case this descriptor is returned.</param>
103     /// <returns>A descriptor based on the current one, with the given set of overrides.</returns>
MergedWith(FeatureSet overrides)104     public FeatureSetDescriptor MergedWith(FeatureSet overrides)
105     {
106         if (overrides is null)
107         {
108             return this;
109         }
110 
111         // Note: It would be nice if we could avoid cloning unless
112         // there are actual changes, but this won't happen that often;
113         // it'll be temporary garbage.
114         var clone = Proto.Clone();
115         clone.MergeFrom(overrides);
116         return cache.GetOrAdd(clone, clone => new FeatureSetDescriptor(clone));
117     }
118 }
119