• 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.Generic;
12 using System.Collections.ObjectModel;
13 using System.Diagnostics;
14 using System.Diagnostics.CodeAnalysis;
15 using System.Linq;
16 
17 namespace Google.Protobuf.Reflection
18 {
19     /// <summary>
20     /// Describes a message type.
21     /// </summary>
22     public sealed class MessageDescriptor : DescriptorBase
23     {
24         private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
25         {
26             "google/protobuf/any.proto",
27             "google/protobuf/api.proto",
28             "google/protobuf/duration.proto",
29             "google/protobuf/empty.proto",
30             "google/protobuf/wrappers.proto",
31             "google/protobuf/timestamp.proto",
32             "google/protobuf/field_mask.proto",
33             "google/protobuf/source_context.proto",
34             "google/protobuf/struct.proto",
35             "google/protobuf/type.proto",
36         };
37 
38         private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
39         private readonly IList<FieldDescriptor> fieldsInNumberOrder;
40         private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
41         private Func<IMessage, bool> extensionSetIsInitialized;
42 
MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)43         internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
44             : base(file, file.ComputeFullName(parent, proto.Name), typeIndex, (parent?.Features ?? file.Features).MergedWith(proto.Options?.Features))
45         {
46             Proto = proto;
47             Parser = generatedCodeInfo?.Parser;
48             ClrType = generatedCodeInfo?.ClrType;
49             ContainingType = parent;
50 
51             // If generatedCodeInfo is null, we just won't generate an accessor for any fields.
52             Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
53                 proto.OneofDecl,
54                 (oneof, index) =>
55                 new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index]));
56 
57             int syntheticOneofCount = 0;
58             foreach (var oneof in Oneofs)
59             {
60                 if (oneof.IsSynthetic)
61                 {
62                     syntheticOneofCount++;
63                 }
64                 else if (syntheticOneofCount != 0)
65                 {
66                     throw new ArgumentException("All synthetic oneofs should come after real oneofs");
67                 }
68             }
69             RealOneofCount = Oneofs.Count - syntheticOneofCount;
70 
71             NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
72                 proto.NestedType,
73                 (type, index) =>
74                 new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index]));
75 
76             EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
77                 proto.EnumType,
78                 (type, index) =>
79                 new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index]));
80 
81             Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
82 
83             fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
84                 proto.Field,
85                 (field, index) =>
86                 new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index], null));
87             fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
88             // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
89             jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder);
90             file.DescriptorPool.AddSymbol(this);
91             Fields = new FieldCollection(this);
92         }
93 
CreateJsonFieldMap(IList<FieldDescriptor> fields)94         private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
95         {
96             var map = new Dictionary<string, FieldDescriptor>();
97             // The ordering is important here: JsonName takes priority over Name,
98             // which means we need to put JsonName values in the map after *all*
99             // Name keys have been added. See https://github.com/protocolbuffers/protobuf/issues/11987
100             foreach (var field in fields)
101             {
102                 map[field.Name] = field;
103             }
104             foreach (var field in fields)
105             {
106                 map[field.JsonName] = field;
107             }
108             return new ReadOnlyDictionary<string, FieldDescriptor>(map);
109         }
110 
111         /// <summary>
112         /// The brief name of the descriptor's target.
113         /// </summary>
114         public override string Name => Proto.Name;
115 
GetNestedDescriptorListForField(int fieldNumber)116         internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
117             fieldNumber switch
118             {
119                 DescriptorProto.FieldFieldNumber => (IReadOnlyList<DescriptorBase>)fieldsInDeclarationOrder,
120                 DescriptorProto.NestedTypeFieldNumber => (IReadOnlyList<DescriptorBase>)NestedTypes,
121                 DescriptorProto.EnumTypeFieldNumber => (IReadOnlyList<DescriptorBase>)EnumTypes,
122                 DescriptorProto.OneofDeclFieldNumber => (IReadOnlyList<DescriptorBase>)Oneofs,
123                 _ => null,
124             };
125 
126         internal DescriptorProto Proto { get; }
127 
128         /// <summary>
129         /// Returns a clone of the underlying <see cref="DescriptorProto"/> describing this message.
130         /// Note that a copy is taken every time this method is called, so clients using it frequently
131         /// (and not modifying it) may want to cache the returned value.
132         /// </summary>
133         /// <returns>A protobuf representation of this message descriptor.</returns>
ToProto()134         public DescriptorProto ToProto() => Proto.Clone();
135 
IsExtensionsInitialized(IMessage message)136         internal bool IsExtensionsInitialized(IMessage message)
137         {
138             if (Proto.ExtensionRange.Count == 0)
139             {
140                 return true;
141             }
142 
143             if (extensionSetIsInitialized == null)
144             {
145                 extensionSetIsInitialized = ReflectionUtil.CreateIsInitializedCaller(ClrType);
146             }
147 
148             return extensionSetIsInitialized(message);
149         }
150 
151         /// <summary>
152         /// The CLR type used to represent message instances from this descriptor.
153         /// </summary>
154         /// <remarks>
155         /// <para>
156         /// The value returned by this property will be non-null for all regular fields. However,
157         /// if a message containing a map field is introspected, the list of nested messages will include
158         /// an auto-generated nested key/value pair message for the field. This is not represented in any
159         /// generated type, so this property will return null in such cases.
160         /// </para>
161         /// <para>
162         /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
163         /// will be the generated message type, not the native type used by reflection for fields of those types. Code
164         /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
165         /// a wrapper type, and handle the result appropriately.
166         /// </para>
167         /// </remarks>
168         [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]
169         public Type ClrType { get; }
170 
171         /// <summary>
172         /// A parser for this message type.
173         /// </summary>
174         /// <remarks>
175         /// <para>
176         /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
177         /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
178         /// </para>
179         /// <para>
180         /// The value returned by this property will be non-null for all regular fields. However,
181         /// if a message containing a map field is introspected, the list of nested messages will include
182         /// an auto-generated nested key/value pair message for the field. No message parser object is created for
183         /// such messages, so this property will return null in such cases.
184         /// </para>
185         /// <para>
186         /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
187         /// will be the generated message type, not the native type used by reflection for fields of those types. Code
188         /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
189         /// a wrapper type, and handle the result appropriately.
190         /// </para>
191         /// </remarks>
192         public MessageParser Parser { get; }
193 
194         /// <summary>
195         /// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
196         /// </summary>
197         internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
198 
199         /// <summary>
200         /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
201         /// with the addition of presence.
202         /// </summary>
203         internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
204 
205         /// <summary>
206         /// Returns whether this message was synthetically-created to store key/value pairs in a
207         /// map field.
208         /// </summary>
209         public bool IsMapEntry => Proto.Options?.MapEntry == true;
210 
211         /// <value>
212         /// If this is a nested type, get the outer descriptor, otherwise null.
213         /// </value>
214         public MessageDescriptor ContainingType { get; }
215 
216         /// <value>
217         /// A collection of fields, which can be retrieved by name or field number.
218         /// </value>
219         public FieldCollection Fields { get; }
220 
221         /// <summary>
222         /// An unmodifiable list of extensions defined in this message's scope.
223         /// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
224         /// if they are declared in a file generated using a version of protoc that did not fully
225         /// support extensions in C#.
226         /// </summary>
227         public ExtensionCollection Extensions { get; }
228 
229         /// <value>
230         /// An unmodifiable list of this message type's nested types.
231         /// </value>
232         public IList<MessageDescriptor> NestedTypes { get; }
233 
234         /// <value>
235         /// An unmodifiable list of this message type's enum types.
236         /// </value>
237         public IList<EnumDescriptor> EnumTypes { get; }
238 
239         /// <value>
240         /// An unmodifiable list of the "oneof" field collections in this message type.
241         /// All "real" oneofs (where <see cref="OneofDescriptor.IsSynthetic"/> returns false)
242         /// come before synthetic ones.
243         /// </value>
244         public IList<OneofDescriptor> Oneofs { get; }
245 
246         /// <summary>
247         /// The number of real "oneof" descriptors in this message type. Every element in <see cref="Oneofs"/>
248         /// with an index less than this will have a <see cref="OneofDescriptor.IsSynthetic"/> property value
249         /// of <c>false</c>; every element with an index greater than or equal to this will have a
250         /// <see cref="OneofDescriptor.IsSynthetic"/> property value of <c>true</c>.
251         /// </summary>
252         public int RealOneofCount { get; }
253 
254         /// <summary>
255         /// Finds a field by field name.
256         /// </summary>
257         /// <param name="name">The unqualified name of the field (e.g. "foo").</param>
258         /// <returns>The field's descriptor, or null if not found.</returns>
FindFieldByName(string name)259         public FieldDescriptor FindFieldByName(string name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
260 
261         /// <summary>
262         /// Finds a field by field number.
263         /// </summary>
264         /// <param name="number">The field number within this message type.</param>
265         /// <returns>The field's descriptor, or null if not found.</returns>
FindFieldByNumber(int number)266         public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
267 
268         /// <summary>
269         /// Finds a nested descriptor by name. The is valid for fields, nested
270         /// message types, oneofs and enums.
271         /// </summary>
272         /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
273         /// <returns>The descriptor, or null if not found.</returns>
274         public T FindDescriptor<T>(string name)  where T : class, IDescriptor =>
275             File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
276 
277         /// <summary>
278         /// The (possibly empty) set of custom options for this message.
279         /// </summary>
280         [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
281         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
282 
283         /// <summary>
284         /// The <c>MessageOptions</c>, defined in <c>descriptor.proto</c>.
285         /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
286         /// Custom options can be retrieved as extensions of the returned message.
287         /// NOTE: A defensive copy is created each time this property is retrieved.
288         /// </summary>
GetOptions()289         public MessageOptions GetOptions()
290         {
291             var clone = Proto.Options?.Clone();
292             if (clone is null)
293             {
294                 return null;
295             }
296             // Clients should be using feature accessor methods, not accessing features on the
297             // options proto.
298             clone.Features = null;
299             return clone;
300         }
301 
302         /// <summary>
303         /// Gets a single value message option for this descriptor
304         /// </summary>
305         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(Extension<MessageOptions, T> extension)306         public T GetOption<T>(Extension<MessageOptions, T> extension)
307         {
308             var value = Proto.Options.GetExtension(extension);
309             return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
310         }
311 
312         /// <summary>
313         /// Gets a repeated value message option for this descriptor
314         /// </summary>
315         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(RepeatedExtension<MessageOptions, T> extension)316         public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension)
317         {
318             return Proto.Options.GetExtension(extension).Clone();
319         }
320 
321         /// <summary>
322         /// Looks up and cross-links all fields and nested types.
323         /// </summary>
CrossLink()324         internal void CrossLink()
325         {
326             foreach (MessageDescriptor message in NestedTypes)
327             {
328                 message.CrossLink();
329             }
330 
331             foreach (FieldDescriptor field in fieldsInDeclarationOrder)
332             {
333                 field.CrossLink();
334             }
335 
336             foreach (OneofDescriptor oneof in Oneofs)
337             {
338                 oneof.CrossLink();
339             }
340 
341             Extensions.CrossLink();
342         }
343 
344         /// <summary>
345         /// A collection to simplify retrieving the field accessor for a particular field.
346         /// </summary>
347         [DebuggerDisplay("Count = {InFieldNumberOrder().Count}")]
348         [DebuggerTypeProxy(typeof(FieldCollectionDebugView))]
349         public sealed class FieldCollection
350         {
351             private readonly MessageDescriptor messageDescriptor;
352 
FieldCollection(MessageDescriptor messageDescriptor)353             internal FieldCollection(MessageDescriptor messageDescriptor)
354             {
355                 this.messageDescriptor = messageDescriptor;
356             }
357 
358             /// <value>
359             /// Returns the fields in the message as an immutable list, in the order in which they
360             /// are declared in the source .proto file.
361             /// </value>
InDeclarationOrder()362             public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
363 
364             /// <value>
365             /// Returns the fields in the message as an immutable list, in ascending field number
366             /// order. Field numbers need not be contiguous, so there is no direct mapping from the
367             /// index in the list to the field number; to retrieve a field by field number, it is better
368             /// to use the <see cref="FieldCollection"/> indexer.
369             /// </value>
InFieldNumberOrder()370             public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
371 
372             // TODO: consider making this public in the future. (Being conservative for now...)
373 
374             /// <value>
375             /// Returns a read-only dictionary mapping the field names in this message as they're available
376             /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
377             /// in the message would result two entries, one with a key <c>fooBar</c> and one with a key
378             /// <c>foo_bar</c>, both referring to the same field.
379             /// </value>
ByJsonName()380             internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
381 
382             /// <summary>
383             /// Retrieves the descriptor for the field with the given number.
384             /// </summary>
385             /// <param name="number">Number of the field to retrieve the descriptor for</param>
386             /// <returns>The accessor for the given field</returns>
387             /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
388             /// with the given number</exception>
389             public FieldDescriptor this[int number]
390             {
391                 get
392                 {
393                     var fieldDescriptor = messageDescriptor.FindFieldByNumber(number);
394                     if (fieldDescriptor == null)
395                     {
396                         throw new KeyNotFoundException("No such field number");
397                     }
398                     return fieldDescriptor;
399                 }
400             }
401 
402             /// <summary>
403             /// Retrieves the descriptor for the field with the given name.
404             /// </summary>
405             /// <param name="name">Name of the field to retrieve the descriptor for</param>
406             /// <returns>The descriptor for the given field</returns>
407             /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
408             /// with the given name</exception>
409             public FieldDescriptor this[string name]
410             {
411                 get
412                 {
413                     var fieldDescriptor = messageDescriptor.FindFieldByName(name);
414                     if (fieldDescriptor == null)
415                     {
416                         throw new KeyNotFoundException("No such field name");
417                     }
418                     return fieldDescriptor;
419                 }
420             }
421 
422             private sealed class FieldCollectionDebugView
423             {
424                 private readonly FieldCollection collection;
425 
FieldCollectionDebugView(FieldCollection collection)426                 public FieldCollectionDebugView(FieldCollection collection)
427                 {
428                     this.collection = collection;
429                 }
430 
431                 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
432                 public FieldDescriptor[] Items => collection.InFieldNumberOrder().ToArray();
433             }
434         }
435     }
436 }
437