• 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.Compatibility;
34 using System;
35 
36 namespace Google.Protobuf.Reflection
37 {
38     /// <summary>
39     /// Descriptor for a field or extension within a message in a .proto file.
40     /// </summary>
41     public sealed class FieldDescriptor : DescriptorBase, IComparable<FieldDescriptor>
42     {
43         private EnumDescriptor enumType;
44         private MessageDescriptor messageType;
45         private FieldType fieldType;
46         private readonly string propertyName; // Annoyingly, needed in Crosslink.
47         private IFieldAccessor accessor;
48 
49         /// <summary>
50         /// Get the field's containing message type.
51         /// </summary>
52         public MessageDescriptor ContainingType { get; }
53 
54         /// <summary>
55         /// Returns the oneof containing this field, or <c>null</c> if it is not part of a oneof.
56         /// </summary>
57         public OneofDescriptor ContainingOneof { get; }
58 
59         /// <summary>
60         /// The effective JSON name for this field. This is usually the lower-camel-cased form of the field name,
61         /// but can be overridden using the <c>json_name</c> option in the .proto file.
62         /// </summary>
63         public string JsonName { get; }
64 
65         internal FieldDescriptorProto Proto { get; }
66 
FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string propertyName)67         internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
68                                  MessageDescriptor parent, int index, string propertyName)
69             : base(file, file.ComputeFullName(parent, proto.Name), index)
70         {
71             Proto = proto;
72             if (proto.Type != 0)
73             {
74                 fieldType = GetFieldTypeFromProtoType(proto.Type);
75             }
76 
77             if (FieldNumber <= 0)
78             {
79                 throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
80             }
81             ContainingType = parent;
82             // OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction.
83             if (proto.OneofIndex != -1)
84             {
85                 if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
86                 {
87                     throw new DescriptorValidationException(this,
88                         $"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}");
89                 }
90                 ContainingOneof = parent.Oneofs[proto.OneofIndex];
91             }
92 
93             file.DescriptorPool.AddSymbol(this);
94             // We can't create the accessor until we've cross-linked, unfortunately, as we
95             // may not know whether the type of the field is a map or not. Remember the property name
96             // for later.
97             // We could trust the generated code and check whether the type of the property is
98             // a MapField, but that feels a tad nasty.
99             this.propertyName = propertyName;
100             JsonName =  Proto.JsonName == "" ? JsonFormatter.ToCamelCase(Proto.Name) : Proto.JsonName;
101         }
102 
103 
104         /// <summary>
105         /// The brief name of the descriptor's target.
106         /// </summary>
107         public override string Name => Proto.Name;
108 
109         /// <summary>
110         /// Returns the accessor for this field.
111         /// </summary>
112         /// <remarks>
113         /// <para>
114         /// While a <see cref="FieldDescriptor"/> describes the field, it does not provide
115         /// any way of obtaining or changing the value of the field within a specific message;
116         /// that is the responsibility of the accessor.
117         /// </para>
118         /// <para>
119         /// The value returned by this property will be non-null for all regular fields. However,
120         /// if a message containing a map field is introspected, the list of nested messages will include
121         /// an auto-generated nested key/value pair message for the field. This is not represented in any
122         /// generated type, and the value of the map field itself is represented by a dictionary in the
123         /// reflection API. There are never instances of those "hidden" messages, so no accessor is provided
124         /// and this property will return null.
125         /// </para>
126         /// </remarks>
127         public IFieldAccessor Accessor => accessor;
128 
129         /// <summary>
130         /// Maps a field type as included in the .proto file to a FieldType.
131         /// </summary>
GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)132         private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
133         {
134             switch (type)
135             {
136                 case FieldDescriptorProto.Types.Type.Double:
137                     return FieldType.Double;
138                 case FieldDescriptorProto.Types.Type.Float:
139                     return FieldType.Float;
140                 case FieldDescriptorProto.Types.Type.Int64:
141                     return FieldType.Int64;
142                 case FieldDescriptorProto.Types.Type.Uint64:
143                     return FieldType.UInt64;
144                 case FieldDescriptorProto.Types.Type.Int32:
145                     return FieldType.Int32;
146                 case FieldDescriptorProto.Types.Type.Fixed64:
147                     return FieldType.Fixed64;
148                 case FieldDescriptorProto.Types.Type.Fixed32:
149                     return FieldType.Fixed32;
150                 case FieldDescriptorProto.Types.Type.Bool:
151                     return FieldType.Bool;
152                 case FieldDescriptorProto.Types.Type.String:
153                     return FieldType.String;
154                 case FieldDescriptorProto.Types.Type.Group:
155                     return FieldType.Group;
156                 case FieldDescriptorProto.Types.Type.Message:
157                     return FieldType.Message;
158                 case FieldDescriptorProto.Types.Type.Bytes:
159                     return FieldType.Bytes;
160                 case FieldDescriptorProto.Types.Type.Uint32:
161                     return FieldType.UInt32;
162                 case FieldDescriptorProto.Types.Type.Enum:
163                     return FieldType.Enum;
164                 case FieldDescriptorProto.Types.Type.Sfixed32:
165                     return FieldType.SFixed32;
166                 case FieldDescriptorProto.Types.Type.Sfixed64:
167                     return FieldType.SFixed64;
168                 case FieldDescriptorProto.Types.Type.Sint32:
169                     return FieldType.SInt32;
170                 case FieldDescriptorProto.Types.Type.Sint64:
171                     return FieldType.SInt64;
172                 default:
173                     throw new ArgumentException("Invalid type specified");
174             }
175         }
176 
177         /// <summary>
178         /// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise.
179         /// </summary>
180         public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
181 
182         /// <summary>
183         /// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
184         /// </summary>
185         public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry;
186 
187         /// <summary>
188         /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise.
189         /// </summary>
190         public bool IsPacked =>
191             // Note the || rather than && here - we're effectively defaulting to packed, because that *is*
192             // the default in proto3, which is all we support. We may give the wrong result for the protos
193             // within descriptor.proto, but that's okay, as they're never exposed and we don't use IsPacked
194             // within the runtime.
195             Proto.Options == null || Proto.Options.Packed;
196 
197         /// <summary>
198         /// Returns the type of the field.
199         /// </summary>
200         public FieldType FieldType => fieldType;
201 
202         /// <summary>
203         /// Returns the field number declared in the proto file.
204         /// </summary>
205         public int FieldNumber => Proto.Number;
206 
207         /// <summary>
208         /// Compares this descriptor with another one, ordering in "canonical" order
209         /// which simply means ascending order by field number. <paramref name="other"/>
210         /// must be a field of the same type, i.e. the <see cref="ContainingType"/> of
211         /// both fields must be the same.
212         /// </summary>
CompareTo(FieldDescriptor other)213         public int CompareTo(FieldDescriptor other)
214         {
215             if (other.ContainingType != ContainingType)
216             {
217                 throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " +
218                                             "for fields of the same message type.");
219             }
220             return FieldNumber - other.FieldNumber;
221         }
222 
223         /// <summary>
224         /// For enum fields, returns the field's type.
225         /// </summary>
226         public EnumDescriptor EnumType
227         {
228             get
229             {
230                 if (fieldType != FieldType.Enum)
231                 {
232                     throw new InvalidOperationException("EnumType is only valid for enum fields.");
233                 }
234                 return enumType;
235             }
236         }
237 
238         /// <summary>
239         /// For embedded message and group fields, returns the field's type.
240         /// </summary>
241         public MessageDescriptor MessageType
242         {
243             get
244             {
245                 if (fieldType != FieldType.Message)
246                 {
247                     throw new InvalidOperationException("MessageType is only valid for message fields.");
248                 }
249                 return messageType;
250             }
251         }
252 
253         /// <summary>
254         /// Look up and cross-link all field types etc.
255         /// </summary>
CrossLink()256         internal void CrossLink()
257         {
258             if (Proto.TypeName != "")
259             {
260                 IDescriptor typeDescriptor =
261                     File.DescriptorPool.LookupSymbol(Proto.TypeName, this);
262 
263                 if (Proto.Type != 0)
264                 {
265                     // Choose field type based on symbol.
266                     if (typeDescriptor is MessageDescriptor)
267                     {
268                         fieldType = FieldType.Message;
269                     }
270                     else if (typeDescriptor is EnumDescriptor)
271                     {
272                         fieldType = FieldType.Enum;
273                     }
274                     else
275                     {
276                         throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
277                     }
278                 }
279 
280                 if (fieldType == FieldType.Message)
281                 {
282                     if (!(typeDescriptor is MessageDescriptor))
283                     {
284                         throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
285                     }
286                     messageType = (MessageDescriptor) typeDescriptor;
287 
288                     if (Proto.DefaultValue != "")
289                     {
290                         throw new DescriptorValidationException(this, "Messages can't have default values.");
291                     }
292                 }
293                 else if (fieldType == FieldType.Enum)
294                 {
295                     if (!(typeDescriptor is EnumDescriptor))
296                     {
297                         throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
298                     }
299                     enumType = (EnumDescriptor) typeDescriptor;
300                 }
301                 else
302                 {
303                     throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
304                 }
305             }
306             else
307             {
308                 if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
309                 {
310                     throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
311                 }
312             }
313 
314             // Note: no attempt to perform any default value parsing
315 
316             File.DescriptorPool.AddFieldByNumber(this);
317 
318             if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
319             {
320                 throw new DescriptorValidationException(this, "MessageSet format is not supported.");
321             }
322             accessor = CreateAccessor();
323         }
324 
CreateAccessor()325         private IFieldAccessor CreateAccessor()
326         {
327             // If we're given no property name, that's because we really don't want an accessor.
328             // (At the moment, that means it's a map entry message...)
329             if (propertyName == null)
330             {
331                 return null;
332             }
333             var property = ContainingType.ClrType.GetProperty(propertyName);
334             if (property == null)
335             {
336                 throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
337             }
338             return IsMap ? new MapFieldAccessor(property, this)
339                 : IsRepeated ? new RepeatedFieldAccessor(property, this)
340                 : (IFieldAccessor) new SingleFieldAccessor(property, this);
341         }
342     }
343 }