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