1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using System; 34 using System.Collections.Generic; 35 using System.Collections.ObjectModel; 36 using System.Linq; 37 #if NET35 38 // Needed for ReadOnlyDictionary, which does not exist in .NET 3.5 39 using Google.Protobuf.Collections; 40 #endif 41 42 namespace Google.Protobuf.Reflection 43 { 44 /// <summary> 45 /// Describes a message type. 46 /// </summary> 47 public sealed class MessageDescriptor : DescriptorBase 48 { 49 private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string> 50 { 51 "google/protobuf/any.proto", 52 "google/protobuf/api.proto", 53 "google/protobuf/duration.proto", 54 "google/protobuf/empty.proto", 55 "google/protobuf/wrappers.proto", 56 "google/protobuf/timestamp.proto", 57 "google/protobuf/field_mask.proto", 58 "google/protobuf/source_context.proto", 59 "google/protobuf/struct.proto", 60 "google/protobuf/type.proto", 61 }; 62 63 private readonly IList<FieldDescriptor> fieldsInDeclarationOrder; 64 private readonly IList<FieldDescriptor> fieldsInNumberOrder; 65 private readonly IDictionary<string, FieldDescriptor> jsonFieldMap; 66 MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)67 internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo) 68 : base(file, file.ComputeFullName(parent, proto.Name), typeIndex) 69 { 70 Proto = proto; 71 Parser = generatedCodeInfo?.Parser; 72 ClrType = generatedCodeInfo?.ClrType; 73 ContainingType = parent; 74 75 // If generatedCodeInfo is null, we just won't generate an accessor for any fields. 76 Oneofs = DescriptorUtil.ConvertAndMakeReadOnly( 77 proto.OneofDecl, 78 (oneof, index) => 79 new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index])); 80 81 NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly( 82 proto.NestedType, 83 (type, index) => 84 new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index])); 85 86 EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly( 87 proto.EnumType, 88 (type, index) => 89 new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index])); 90 91 Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions); 92 93 fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly( 94 proto.Field, 95 (field, index) => 96 new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index], null)); 97 fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray()); 98 // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.) 99 jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder); 100 file.DescriptorPool.AddSymbol(this); 101 Fields = new FieldCollection(this); 102 } 103 CreateJsonFieldMap(IList<FieldDescriptor> fields)104 private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields) 105 { 106 var map = new Dictionary<string, FieldDescriptor>(); 107 foreach (var field in fields) 108 { 109 map[field.Name] = field; 110 map[field.JsonName] = field; 111 } 112 return new ReadOnlyDictionary<string, FieldDescriptor>(map); 113 } 114 115 /// <summary> 116 /// The brief name of the descriptor's target. 117 /// </summary> 118 public override string Name => Proto.Name; 119 GetNestedDescriptorListForField(int fieldNumber)120 internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) 121 { 122 switch (fieldNumber) 123 { 124 case DescriptorProto.FieldFieldNumber: 125 return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder; 126 case DescriptorProto.NestedTypeFieldNumber: 127 return (IReadOnlyList<DescriptorBase>) NestedTypes; 128 case DescriptorProto.EnumTypeFieldNumber: 129 return (IReadOnlyList<DescriptorBase>) EnumTypes; 130 default: 131 return null; 132 } 133 } 134 135 internal DescriptorProto Proto { get; } 136 137 /// <summary> 138 /// The CLR type used to represent message instances from this descriptor. 139 /// </summary> 140 /// <remarks> 141 /// <para> 142 /// The value returned by this property will be non-null for all regular fields. However, 143 /// if a message containing a map field is introspected, the list of nested messages will include 144 /// an auto-generated nested key/value pair message for the field. This is not represented in any 145 /// generated type, so this property will return null in such cases. 146 /// </para> 147 /// <para> 148 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here 149 /// will be the generated message type, not the native type used by reflection for fields of those types. Code 150 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents 151 /// a wrapper type, and handle the result appropriately. 152 /// </para> 153 /// </remarks> 154 public Type ClrType { get; } 155 156 /// <summary> 157 /// A parser for this message type. 158 /// </summary> 159 /// <remarks> 160 /// <para> 161 /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically 162 /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>. 163 /// </para> 164 /// <para> 165 /// The value returned by this property will be non-null for all regular fields. However, 166 /// if a message containing a map field is introspected, the list of nested messages will include 167 /// an auto-generated nested key/value pair message for the field. No message parser object is created for 168 /// such messages, so this property will return null in such cases. 169 /// </para> 170 /// <para> 171 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here 172 /// will be the generated message type, not the native type used by reflection for fields of those types. Code 173 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents 174 /// a wrapper type, and handle the result appropriately. 175 /// </para> 176 /// </remarks> 177 public MessageParser Parser { get; } 178 179 /// <summary> 180 /// Returns whether this message is one of the "well known types" which may have runtime/protoc support. 181 /// </summary> 182 internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name); 183 184 /// <summary> 185 /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values 186 /// with the addition of presence. 187 /// </summary> 188 internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto"; 189 190 /// <value> 191 /// If this is a nested type, get the outer descriptor, otherwise null. 192 /// </value> 193 public MessageDescriptor ContainingType { get; } 194 195 /// <value> 196 /// A collection of fields, which can be retrieved by name or field number. 197 /// </value> 198 public FieldCollection Fields { get; } 199 200 /// <summary> 201 /// An unmodifiable list of extensions defined in this message's scrope 202 /// </summary> 203 public ExtensionCollection Extensions { get; } 204 205 /// <value> 206 /// An unmodifiable list of this message type's nested types. 207 /// </value> 208 public IList<MessageDescriptor> NestedTypes { get; } 209 210 /// <value> 211 /// An unmodifiable list of this message type's enum types. 212 /// </value> 213 public IList<EnumDescriptor> EnumTypes { get; } 214 215 /// <value> 216 /// An unmodifiable list of the "oneof" field collections in this message type. 217 /// </value> 218 public IList<OneofDescriptor> Oneofs { get; } 219 220 /// <summary> 221 /// Finds a field by field name. 222 /// </summary> 223 /// <param name="name">The unqualified name of the field (e.g. "foo").</param> 224 /// <returns>The field's descriptor, or null if not found.</returns> 225 public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name); 226 227 /// <summary> 228 /// Finds a field by field number. 229 /// </summary> 230 /// <param name="number">The field number within this message type.</param> 231 /// <returns>The field's descriptor, or null if not found.</returns> FindFieldByNumber(int number)232 public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number); 233 234 /// <summary> 235 /// Finds a nested descriptor by name. The is valid for fields, nested 236 /// message types, oneofs and enums. 237 /// </summary> 238 /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param> 239 /// <returns>The descriptor, or null if not found.</returns> 240 public T FindDescriptor<T>(string name) where T : class, IDescriptor => 241 File.DescriptorPool.FindSymbol<T>(FullName + "." + name); 242 243 /// <summary> 244 /// The (possibly empty) set of custom options for this message. 245 /// </summary> 246 //[Obsolete("CustomOptions are obsolete. Use GetOption")] 247 public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber); 248 249 /* // uncomment this in the full proto2 support PR 250 /// <summary> 251 /// Gets a single value enum option for this descriptor 252 /// </summary> 253 public T GetOption<T>(Extension<MessageOptions, T> extension) 254 { 255 var value = Proto.Options.GetExtension(extension); 256 return value is IDeepCloneable<T> clonable ? clonable.Clone() : value; 257 } 258 259 /// <summary> 260 /// Gets a repeated value enum option for this descriptor 261 /// </summary> 262 public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension) 263 { 264 return Proto.Options.GetExtension(extension).Clone(); 265 } 266 */ 267 268 /// <summary> 269 /// Looks up and cross-links all fields and nested types. 270 /// </summary> CrossLink()271 internal void CrossLink() 272 { 273 foreach (MessageDescriptor message in NestedTypes) 274 { 275 message.CrossLink(); 276 } 277 278 foreach (FieldDescriptor field in fieldsInDeclarationOrder) 279 { 280 field.CrossLink(); 281 } 282 283 foreach (OneofDescriptor oneof in Oneofs) 284 { 285 oneof.CrossLink(); 286 } 287 288 Extensions.CrossLink(); 289 } 290 291 /// <summary> 292 /// A collection to simplify retrieving the field accessor for a particular field. 293 /// </summary> 294 public sealed class FieldCollection 295 { 296 private readonly MessageDescriptor messageDescriptor; 297 FieldCollection(MessageDescriptor messageDescriptor)298 internal FieldCollection(MessageDescriptor messageDescriptor) 299 { 300 this.messageDescriptor = messageDescriptor; 301 } 302 303 /// <value> 304 /// Returns the fields in the message as an immutable list, in the order in which they 305 /// are declared in the source .proto file. 306 /// </value> InDeclarationOrder()307 public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder; 308 309 /// <value> 310 /// Returns the fields in the message as an immutable list, in ascending field number 311 /// order. Field numbers need not be contiguous, so there is no direct mapping from the 312 /// index in the list to the field number; to retrieve a field by field number, it is better 313 /// to use the <see cref="FieldCollection"/> indexer. 314 /// </value> InFieldNumberOrder()315 public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder; 316 317 // TODO: consider making this public in the future. (Being conservative for now...) 318 319 /// <value> 320 /// Returns a read-only dictionary mapping the field names in this message as they're available 321 /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c> 322 /// in the message would result two entries, one with a key <c>fooBar</c> and one with a key 323 /// <c>foo_bar</c>, both referring to the same field. 324 /// </value> ByJsonName()325 internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap; 326 327 /// <summary> 328 /// Retrieves the descriptor for the field with the given number. 329 /// </summary> 330 /// <param name="number">Number of the field to retrieve the descriptor for</param> 331 /// <returns>The accessor for the given field</returns> 332 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field 333 /// with the given number</exception> 334 public FieldDescriptor this[int number] 335 { 336 get 337 { 338 var fieldDescriptor = messageDescriptor.FindFieldByNumber(number); 339 if (fieldDescriptor == null) 340 { 341 throw new KeyNotFoundException("No such field number"); 342 } 343 return fieldDescriptor; 344 } 345 } 346 347 /// <summary> 348 /// Retrieves the descriptor for the field with the given name. 349 /// </summary> 350 /// <param name="name">Name of the field to retrieve the descriptor for</param> 351 /// <returns>The descriptor for the given field</returns> 352 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field 353 /// with the given name</exception> 354 public FieldDescriptor this[string name] 355 { 356 get 357 { 358 var fieldDescriptor = messageDescriptor.FindFieldByName(name); 359 if (fieldDescriptor == null) 360 { 361 throw new KeyNotFoundException("No such field name"); 362 } 363 return fieldDescriptor; 364 } 365 } 366 } 367 } 368 } 369