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