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