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 38 namespace Google.Protobuf.Reflection 39 { 40 /// <summary> 41 /// Describes a message type. 42 /// </summary> 43 public sealed class MessageDescriptor : DescriptorBase 44 { 45 private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string> 46 { 47 "google/protobuf/any.proto", 48 "google/protobuf/api.proto", 49 "google/protobuf/duration.proto", 50 "google/protobuf/empty.proto", 51 "google/protobuf/wrappers.proto", 52 "google/protobuf/timestamp.proto", 53 "google/protobuf/field_mask.proto", 54 "google/protobuf/source_context.proto", 55 "google/protobuf/struct.proto", 56 "google/protobuf/type.proto", 57 }; 58 59 private readonly IList<FieldDescriptor> fieldsInDeclarationOrder; 60 private readonly IList<FieldDescriptor> fieldsInNumberOrder; 61 private readonly IDictionary<string, FieldDescriptor> jsonFieldMap; 62 MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)63 internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo) 64 : base(file, file.ComputeFullName(parent, proto.Name), typeIndex) 65 { 66 Proto = proto; 67 Parser = generatedCodeInfo?.Parser; 68 ClrType = generatedCodeInfo?.ClrType; 69 ContainingType = parent; 70 71 // Note use of generatedCodeInfo. rather than generatedCodeInfo?. here... we don't expect 72 // to see any nested oneofs, types or enums in "not actually generated" code... we do 73 // expect fields though (for map entry messages). 74 Oneofs = DescriptorUtil.ConvertAndMakeReadOnly( 75 proto.OneofDecl, 76 (oneof, index) => 77 new OneofDescriptor(oneof, file, this, index, generatedCodeInfo.OneofNames[index])); 78 79 NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly( 80 proto.NestedType, 81 (type, index) => 82 new MessageDescriptor(type, file, this, index, generatedCodeInfo.NestedTypes[index])); 83 84 EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly( 85 proto.EnumType, 86 (type, index) => 87 new EnumDescriptor(type, file, this, index, generatedCodeInfo.NestedEnums[index])); 88 89 fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly( 90 proto.Field, 91 (field, index) => 92 new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index])); 93 fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray()); 94 // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.) 95 jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder); 96 file.DescriptorPool.AddSymbol(this); 97 Fields = new FieldCollection(this); 98 } 99 CreateJsonFieldMap(IList<FieldDescriptor> fields)100 private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields) 101 { 102 var map = new Dictionary<string, FieldDescriptor>(); 103 foreach (var field in fields) 104 { 105 map[field.Name] = field; 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 116 internal DescriptorProto Proto { get; } 117 118 /// <summary> 119 /// The CLR type used to represent message instances from this descriptor. 120 /// </summary> 121 /// <remarks> 122 /// <para> 123 /// The value returned by this property will be non-null for all regular fields. However, 124 /// if a message containing a map field is introspected, the list of nested messages will include 125 /// an auto-generated nested key/value pair message for the field. This is not represented in any 126 /// generated type, so this property will return null in such cases. 127 /// </para> 128 /// <para> 129 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here 130 /// will be the generated message type, not the native type used by reflection for fields of those types. Code 131 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents 132 /// a wrapper type, and handle the result appropriately. 133 /// </para> 134 /// </remarks> 135 public Type ClrType { get; } 136 137 /// <summary> 138 /// A parser for this message type. 139 /// </summary> 140 /// <remarks> 141 /// <para> 142 /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically 143 /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>. 144 /// </para> 145 /// <para> 146 /// The value returned by this property will be non-null for all regular fields. However, 147 /// if a message containing a map field is introspected, the list of nested messages will include 148 /// an auto-generated nested key/value pair message for the field. No message parser object is created for 149 /// such messages, so this property will return null in such cases. 150 /// </para> 151 /// <para> 152 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here 153 /// will be the generated message type, not the native type used by reflection for fields of those types. Code 154 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents 155 /// a wrapper type, and handle the result appropriately. 156 /// </para> 157 /// </remarks> 158 public MessageParser Parser { get; } 159 160 /// <summary> 161 /// Returns whether this message is one of the "well known types" which may have runtime/protoc support. 162 /// </summary> 163 internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name); 164 165 /// <summary> 166 /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values 167 /// with the addition of presence. 168 /// </summary> 169 internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto"; 170 171 /// <value> 172 /// If this is a nested type, get the outer descriptor, otherwise null. 173 /// </value> 174 public MessageDescriptor ContainingType { get; } 175 176 /// <value> 177 /// A collection of fields, which can be retrieved by name or field number. 178 /// </value> 179 public FieldCollection Fields { get; } 180 181 /// <value> 182 /// An unmodifiable list of this message type's nested types. 183 /// </value> 184 public IList<MessageDescriptor> NestedTypes { get; } 185 186 /// <value> 187 /// An unmodifiable list of this message type's enum types. 188 /// </value> 189 public IList<EnumDescriptor> EnumTypes { get; } 190 191 /// <value> 192 /// An unmodifiable list of the "oneof" field collections in this message type. 193 /// </value> 194 public IList<OneofDescriptor> Oneofs { get; } 195 196 /// <summary> 197 /// Finds a field by field name. 198 /// </summary> 199 /// <param name="name">The unqualified name of the field (e.g. "foo").</param> 200 /// <returns>The field's descriptor, or null if not found.</returns> 201 public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name); 202 203 /// <summary> 204 /// Finds a field by field number. 205 /// </summary> 206 /// <param name="number">The field number within this message type.</param> 207 /// <returns>The field's descriptor, or null if not found.</returns> FindFieldByNumber(int number)208 public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number); 209 210 /// <summary> 211 /// Finds a nested descriptor by name. The is valid for fields, nested 212 /// message types, oneofs and enums. 213 /// </summary> 214 /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param> 215 /// <returns>The descriptor, or null if not found.</returns> 216 public T FindDescriptor<T>(string name) where T : class, IDescriptor => 217 File.DescriptorPool.FindSymbol<T>(FullName + "." + name); 218 219 /// <summary> 220 /// Looks up and cross-links all fields and nested types. 221 /// </summary> CrossLink()222 internal void CrossLink() 223 { 224 foreach (MessageDescriptor message in NestedTypes) 225 { 226 message.CrossLink(); 227 } 228 229 foreach (FieldDescriptor field in fieldsInDeclarationOrder) 230 { 231 field.CrossLink(); 232 } 233 234 foreach (OneofDescriptor oneof in Oneofs) 235 { 236 oneof.CrossLink(); 237 } 238 } 239 240 /// <summary> 241 /// A collection to simplify retrieving the field accessor for a particular field. 242 /// </summary> 243 public sealed class FieldCollection 244 { 245 private readonly MessageDescriptor messageDescriptor; 246 FieldCollection(MessageDescriptor messageDescriptor)247 internal FieldCollection(MessageDescriptor messageDescriptor) 248 { 249 this.messageDescriptor = messageDescriptor; 250 } 251 252 /// <value> 253 /// Returns the fields in the message as an immutable list, in the order in which they 254 /// are declared in the source .proto file. 255 /// </value> InDeclarationOrder()256 public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder; 257 258 /// <value> 259 /// Returns the fields in the message as an immutable list, in ascending field number 260 /// order. Field numbers need not be contiguous, so there is no direct mapping from the 261 /// index in the list to the field number; to retrieve a field by field number, it is better 262 /// to use the <see cref="FieldCollection"/> indexer. 263 /// </value> InFieldNumberOrder()264 public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder; 265 266 // TODO: consider making this public in the future. (Being conservative for now...) 267 268 /// <value> 269 /// Returns a read-only dictionary mapping the field names in this message as they're available 270 /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c> 271 /// in the message would result two entries, one with a key <c>fooBar</c> and one with a key 272 /// <c>foo_bar</c>, both referring to the same field. 273 /// </value> ByJsonName()274 internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap; 275 276 /// <summary> 277 /// Retrieves the descriptor for the field with the given number. 278 /// </summary> 279 /// <param name="number">Number of the field to retrieve the descriptor for</param> 280 /// <returns>The accessor for the given field</returns> 281 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field 282 /// with the given number</exception> 283 public FieldDescriptor this[int number] 284 { 285 get 286 { 287 var fieldDescriptor = messageDescriptor.FindFieldByNumber(number); 288 if (fieldDescriptor == null) 289 { 290 throw new KeyNotFoundException("No such field number"); 291 } 292 return fieldDescriptor; 293 } 294 } 295 296 /// <summary> 297 /// Retrieves the descriptor for the field with the given name. 298 /// </summary> 299 /// <param name="name">Name of the field to retrieve the descriptor for</param> 300 /// <returns>The descriptor for the given field</returns> 301 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field 302 /// with the given name</exception> 303 public FieldDescriptor this[string name] 304 { 305 get 306 { 307 var fieldDescriptor = messageDescriptor.FindFieldByName(name); 308 if (fieldDescriptor == null) 309 { 310 throw new KeyNotFoundException("No such field name"); 311 } 312 return fieldDescriptor; 313 } 314 } 315 } 316 } 317 } 318