xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
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