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 Google.Protobuf.Collections; 34 using Google.Protobuf.Compatibility; 35 using System; 36 37 namespace Google.Protobuf.Reflection 38 { 39 /// <summary> 40 /// Descriptor for a field or extension within a message in a .proto file. 41 /// </summary> 42 public sealed class FieldDescriptor : DescriptorBase, IComparable<FieldDescriptor> 43 { 44 private EnumDescriptor enumType; 45 private MessageDescriptor extendeeType; 46 private MessageDescriptor messageType; 47 private FieldType fieldType; 48 private IFieldAccessor accessor; 49 50 /// <summary> 51 /// Get the field's containing message type, or <c>null</c> if it is a field defined at the top level of a file as an extension. 52 /// </summary> 53 public MessageDescriptor ContainingType { get; } 54 55 /// <summary> 56 /// Returns the oneof containing this field, or <c>null</c> if it is not part of a oneof. 57 /// </summary> 58 public OneofDescriptor ContainingOneof { get; } 59 60 /// <summary> 61 /// Returns the oneof containing this field if it's a "real" oneof, or <c>null</c> if either this 62 /// field is not part of a oneof, or the oneof is synthetic. 63 /// </summary> 64 public OneofDescriptor RealContainingOneof => ContainingOneof?.IsSynthetic == false ? ContainingOneof : null; 65 66 /// <summary> 67 /// The effective JSON name for this field. This is usually the lower-camel-cased form of the field name, 68 /// but can be overridden using the <c>json_name</c> option in the .proto file. 69 /// </summary> 70 public string JsonName { get; } 71 72 /// <summary> 73 /// The name of the property in the <c>ContainingType.ClrType</c> class. 74 /// </summary> 75 public string PropertyName { get; } 76 77 /// <summary> 78 /// Indicates whether this field supports presence, either implicitly (e.g. due to it being a message 79 /// type field) or explicitly via Has/Clear members. If this returns true, it is safe to call 80 /// <see cref="IFieldAccessor.Clear(IMessage)"/> and <see cref="IFieldAccessor.HasValue(IMessage)"/> 81 /// on this field's accessor with a suitable message. 82 /// </summary> 83 public bool HasPresence => 84 Extension != null ? !Extension.IsRepeated 85 : IsRepeated ? false 86 : IsMap ? false 87 : FieldType == FieldType.Message ? true 88 // This covers "real oneof members" and "proto3 optional fields" 89 : ContainingOneof != null ? true 90 : File.Syntax == Syntax.Proto2; 91 92 internal FieldDescriptorProto Proto { get; } 93 94 /// <summary> 95 /// Returns a clone of the underlying <see cref="FieldDescriptorProto"/> describing this field. 96 /// Note that a copy is taken every time this method is called, so clients using it frequently 97 /// (and not modifying it) may want to cache the returned value. 98 /// </summary> 99 /// <returns>A protobuf representation of this field descriptor.</returns> ToProto()100 public FieldDescriptorProto ToProto() => Proto.Clone(); 101 102 /// <summary> 103 /// An extension identifier for this field, or <c>null</c> if this field isn't an extension. 104 /// </summary> 105 public Extension Extension { get; } 106 FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string propertyName, Extension extension)107 internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, 108 MessageDescriptor parent, int index, string propertyName, Extension extension) 109 : base(file, file.ComputeFullName(parent, proto.Name), index) 110 { 111 Proto = proto; 112 if (proto.Type != 0) 113 { 114 fieldType = GetFieldTypeFromProtoType(proto.Type); 115 } 116 117 if (FieldNumber <= 0) 118 { 119 throw new DescriptorValidationException(this, "Field numbers must be positive integers."); 120 } 121 ContainingType = parent; 122 if (proto.HasOneofIndex) 123 { 124 if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count) 125 { 126 throw new DescriptorValidationException(this, 127 $"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}"); 128 } 129 ContainingOneof = parent.Oneofs[proto.OneofIndex]; 130 } 131 132 file.DescriptorPool.AddSymbol(this); 133 // We can't create the accessor until we've cross-linked, unfortunately, as we 134 // may not know whether the type of the field is a map or not. Remember the property name 135 // for later. 136 // We could trust the generated code and check whether the type of the property is 137 // a MapField, but that feels a tad nasty. 138 PropertyName = propertyName; 139 Extension = extension; 140 JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName; 141 } 142 143 144 /// <summary> 145 /// The brief name of the descriptor's target. 146 /// </summary> 147 public override string Name => Proto.Name; 148 149 /// <summary> 150 /// Returns the accessor for this field. 151 /// </summary> 152 /// <remarks> 153 /// <para> 154 /// While a <see cref="FieldDescriptor"/> describes the field, it does not provide 155 /// any way of obtaining or changing the value of the field within a specific message; 156 /// that is the responsibility of the accessor. 157 /// </para> 158 /// <para> 159 /// In descriptors for generated code, the value returned by this property will be non-null for all 160 /// regular fields. However, if a message containing a map field is introspected, the list of nested messages will include 161 /// an auto-generated nested key/value pair message for the field. This is not represented in any 162 /// generated type, and the value of the map field itself is represented by a dictionary in the 163 /// reflection API. There are never instances of those "hidden" messages, so no accessor is provided 164 /// and this property will return null. 165 /// </para> 166 /// <para> 167 /// In dynamically loaded descriptors, the value returned by this property will current be null; 168 /// if and when dynamic messages are supported, it will return a suitable accessor to work with 169 /// them. 170 /// </para> 171 /// </remarks> 172 public IFieldAccessor Accessor => accessor; 173 174 /// <summary> 175 /// Maps a field type as included in the .proto file to a FieldType. 176 /// </summary> GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)177 private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type) 178 { 179 switch (type) 180 { 181 case FieldDescriptorProto.Types.Type.Double: 182 return FieldType.Double; 183 case FieldDescriptorProto.Types.Type.Float: 184 return FieldType.Float; 185 case FieldDescriptorProto.Types.Type.Int64: 186 return FieldType.Int64; 187 case FieldDescriptorProto.Types.Type.Uint64: 188 return FieldType.UInt64; 189 case FieldDescriptorProto.Types.Type.Int32: 190 return FieldType.Int32; 191 case FieldDescriptorProto.Types.Type.Fixed64: 192 return FieldType.Fixed64; 193 case FieldDescriptorProto.Types.Type.Fixed32: 194 return FieldType.Fixed32; 195 case FieldDescriptorProto.Types.Type.Bool: 196 return FieldType.Bool; 197 case FieldDescriptorProto.Types.Type.String: 198 return FieldType.String; 199 case FieldDescriptorProto.Types.Type.Group: 200 return FieldType.Group; 201 case FieldDescriptorProto.Types.Type.Message: 202 return FieldType.Message; 203 case FieldDescriptorProto.Types.Type.Bytes: 204 return FieldType.Bytes; 205 case FieldDescriptorProto.Types.Type.Uint32: 206 return FieldType.UInt32; 207 case FieldDescriptorProto.Types.Type.Enum: 208 return FieldType.Enum; 209 case FieldDescriptorProto.Types.Type.Sfixed32: 210 return FieldType.SFixed32; 211 case FieldDescriptorProto.Types.Type.Sfixed64: 212 return FieldType.SFixed64; 213 case FieldDescriptorProto.Types.Type.Sint32: 214 return FieldType.SInt32; 215 case FieldDescriptorProto.Types.Type.Sint64: 216 return FieldType.SInt64; 217 default: 218 throw new ArgumentException("Invalid type specified"); 219 } 220 } 221 222 /// <summary> 223 /// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise. 224 /// </summary> 225 public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated; 226 227 /// <summary> 228 /// Returns <c>true</c> if this field is a required field; <c>false</c> otherwise. 229 /// </summary> 230 public bool IsRequired => Proto.Label == FieldDescriptorProto.Types.Label.Required; 231 232 /// <summary> 233 /// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise. 234 /// </summary> 235 public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry; 236 237 /// <summary> 238 /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise. 239 /// </summary> 240 public bool IsPacked 241 { 242 get 243 { 244 if (File.Syntax != Syntax.Proto3) 245 { 246 return Proto.Options?.Packed ?? false; 247 } 248 else 249 { 250 // Packed by default with proto3 251 return Proto.Options == null || !Proto.Options.HasPacked || Proto.Options.Packed; 252 } 253 } 254 } 255 256 /// <summary> 257 /// Returns <c>true</c> if this field extends another message type; <c>false</c> otherwise. 258 /// </summary> 259 public bool IsExtension => Proto.HasExtendee; 260 261 /// <summary> 262 /// Returns the type of the field. 263 /// </summary> 264 public FieldType FieldType => fieldType; 265 266 /// <summary> 267 /// Returns the field number declared in the proto file. 268 /// </summary> 269 public int FieldNumber => Proto.Number; 270 271 /// <summary> 272 /// Compares this descriptor with another one, ordering in "canonical" order 273 /// which simply means ascending order by field number. <paramref name="other"/> 274 /// must be a field of the same type, i.e. the <see cref="ContainingType"/> of 275 /// both fields must be the same. 276 /// </summary> CompareTo(FieldDescriptor other)277 public int CompareTo(FieldDescriptor other) 278 { 279 if (other.ContainingType != ContainingType) 280 { 281 throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " + 282 "for fields of the same message type."); 283 } 284 return FieldNumber - other.FieldNumber; 285 } 286 287 /// <summary> 288 /// For enum fields, returns the field's type. 289 /// </summary> 290 public EnumDescriptor EnumType 291 { 292 get 293 { 294 if (fieldType != FieldType.Enum) 295 { 296 throw new InvalidOperationException("EnumType is only valid for enum fields."); 297 } 298 return enumType; 299 } 300 } 301 302 /// <summary> 303 /// For embedded message and group fields, returns the field's type. 304 /// </summary> 305 public MessageDescriptor MessageType 306 { 307 get 308 { 309 if (fieldType != FieldType.Message && fieldType != FieldType.Group) 310 { 311 throw new InvalidOperationException("MessageType is only valid for message or group fields."); 312 } 313 return messageType; 314 } 315 } 316 317 /// <summary> 318 /// For extension fields, returns the extended type 319 /// </summary> 320 public MessageDescriptor ExtendeeType 321 { 322 get 323 { 324 if (!Proto.HasExtendee) 325 { 326 throw new InvalidOperationException("ExtendeeType is only valid for extension fields."); 327 } 328 return extendeeType; 329 } 330 } 331 332 /// <summary> 333 /// The (possibly empty) set of custom options for this field. 334 /// </summary> 335 [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")] 336 public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber); 337 338 /// <summary> 339 /// The <c>FieldOptions</c>, defined in <c>descriptor.proto</c>. 340 /// If the options message is not present (i.e. there are no options), <c>null</c> is returned. 341 /// Custom options can be retrieved as extensions of the returned message. 342 /// NOTE: A defensive copy is created each time this property is retrieved. 343 /// </summary> GetOptions()344 public FieldOptions GetOptions() => Proto.Options?.Clone(); 345 346 /// <summary> 347 /// Gets a single value field option for this descriptor 348 /// </summary> 349 [Obsolete("GetOption is obsolete. Use the GetOptions() method.")] GetOption(Extension<FieldOptions, T> extension)350 public T GetOption<T>(Extension<FieldOptions, T> extension) 351 { 352 var value = Proto.Options.GetExtension(extension); 353 return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value; 354 } 355 356 /// <summary> 357 /// Gets a repeated value field option for this descriptor 358 /// </summary> 359 [Obsolete("GetOption is obsolete. Use the GetOptions() method.")] GetOption(RepeatedExtension<FieldOptions, T> extension)360 public RepeatedField<T> GetOption<T>(RepeatedExtension<FieldOptions, T> extension) 361 { 362 return Proto.Options.GetExtension(extension).Clone(); 363 } 364 365 /// <summary> 366 /// Look up and cross-link all field types etc. 367 /// </summary> CrossLink()368 internal void CrossLink() 369 { 370 if (Proto.HasTypeName) 371 { 372 IDescriptor typeDescriptor = 373 File.DescriptorPool.LookupSymbol(Proto.TypeName, this); 374 375 if (Proto.HasType) 376 { 377 // Choose field type based on symbol. 378 if (typeDescriptor is MessageDescriptor) 379 { 380 fieldType = FieldType.Message; 381 } 382 else if (typeDescriptor is EnumDescriptor) 383 { 384 fieldType = FieldType.Enum; 385 } 386 else 387 { 388 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type."); 389 } 390 } 391 392 if (fieldType == FieldType.Message || fieldType == FieldType.Group) 393 { 394 if (!(typeDescriptor is MessageDescriptor)) 395 { 396 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type."); 397 } 398 messageType = (MessageDescriptor) typeDescriptor; 399 400 if (Proto.HasDefaultValue) 401 { 402 throw new DescriptorValidationException(this, "Messages can't have default values."); 403 } 404 } 405 else if (fieldType == FieldType.Enum) 406 { 407 if (!(typeDescriptor is EnumDescriptor)) 408 { 409 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type."); 410 } 411 enumType = (EnumDescriptor) typeDescriptor; 412 } 413 else 414 { 415 throw new DescriptorValidationException(this, "Field with primitive type has type_name."); 416 } 417 } 418 else 419 { 420 if (fieldType == FieldType.Message || fieldType == FieldType.Enum) 421 { 422 throw new DescriptorValidationException(this, "Field with message or enum type missing type_name."); 423 } 424 } 425 426 if (Proto.HasExtendee) 427 { 428 extendeeType = File.DescriptorPool.LookupSymbol(Proto.Extendee, this) as MessageDescriptor; 429 } 430 431 // Note: no attempt to perform any default value parsing 432 433 File.DescriptorPool.AddFieldByNumber(this); 434 435 if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat) 436 { 437 throw new DescriptorValidationException(this, "MessageSet format is not supported."); 438 } 439 accessor = CreateAccessor(); 440 } 441 CreateAccessor()442 private IFieldAccessor CreateAccessor() 443 { 444 if (Extension != null) 445 { 446 return new ExtensionAccessor(this); 447 } 448 449 // If we're given no property name, that's because we really don't want an accessor. 450 // This could be because it's a map message, or it could be that we're loading a FileDescriptor dynamically. 451 // TODO: Support dynamic messages. 452 if (PropertyName == null) 453 { 454 return null; 455 } 456 457 var property = ContainingType.ClrType.GetProperty(PropertyName); 458 if (property == null) 459 { 460 throw new DescriptorValidationException(this, $"Property {PropertyName} not found in {ContainingType.ClrType}"); 461 } 462 return IsMap ? new MapFieldAccessor(property, this) 463 : IsRepeated ? new RepeatedFieldAccessor(property, this) 464 : (IFieldAccessor) new SingleFieldAccessor(ContainingType.ClrType, property, this); 465 } 466 } 467 } 468