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