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