blob: 2a3d5c7a2dbabb3a9bdc7dfb1831fce3147c0d26 [file] [log] [blame]
Jon Skeet9f37de92015-07-14 10:24:52 +01001#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
Jon Skeet0dbd5ec2015-07-23 15:31:34 +010033using Google.Protobuf.Compatibility;
Jon Skeet71e8dca2016-03-30 09:42:37 +010034using System;
Jon Skeet9f37de92015-07-14 10:24:52 +010035
36namespace 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 {
Jon Skeet9f37de92015-07-14 10:24:52 +010043 private EnumDescriptor enumType;
44 private MessageDescriptor messageType;
Jon Skeet9f37de92015-07-14 10:24:52 +010045 private FieldType fieldType;
Jon Skeet4668c3d2015-07-22 11:38:22 +010046 private readonly string propertyName; // Annoyingly, needed in Crosslink.
Jon Skeet53c399a2015-07-20 19:24:31 +010047 private IFieldAccessor accessor;
Jon Skeet9f37de92015-07-14 10:24:52 +010048
Jon Skeet71e8dca2016-03-30 09:42:37 +010049 /// <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
Jon Skeet9f37de92015-07-14 10:24:52 +010067 internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
Jon Skeet4668c3d2015-07-22 11:38:22 +010068 MessageDescriptor parent, int index, string propertyName)
Jon Skeet9f37de92015-07-14 10:24:52 +010069 : base(file, file.ComputeFullName(parent, proto.Name), index)
70 {
Jon Skeet71e8dca2016-03-30 09:42:37 +010071 Proto = proto;
Jon Skeet9f37de92015-07-14 10:24:52 +010072 if (proto.Type != 0)
73 {
74 fieldType = GetFieldTypeFromProtoType(proto.Type);
75 }
76
77 if (FieldNumber <= 0)
78 {
Jon Skeet72ec3362015-11-19 17:13:38 +000079 throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
Jon Skeet9f37de92015-07-14 10:24:52 +010080 }
Jon Skeet71e8dca2016-03-30 09:42:37 +010081 ContainingType = parent;
Jon Skeet9f37de92015-07-14 10:24:52 +010082 // 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,
Jon Skeet72ec3362015-11-19 17:13:38 +000088 $"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}");
Jon Skeet9f37de92015-07-14 10:24:52 +010089 }
Jon Skeet71e8dca2016-03-30 09:42:37 +010090 ContainingOneof = parent.Oneofs[proto.OneofIndex];
Jon Skeet9f37de92015-07-14 10:24:52 +010091 }
92
93 file.DescriptorPool.AddSymbol(this);
Jon Skeet4668c3d2015-07-22 11:38:22 +010094 // 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;
Jon Skeet50a37e02016-11-03 09:24:29 +0000100 JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
Jon Skeet9f37de92015-07-14 10:24:52 +0100101 }
Jon Skeet71e8dca2016-03-30 09:42:37 +0100102
Jon Skeet9f37de92015-07-14 10:24:52 +0100103
104 /// <summary>
105 /// The brief name of the descriptor's target.
106 /// </summary>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100107 public override string Name => Proto.Name;
Jon Skeet53c399a2015-07-20 19:24:31 +0100108
Jon Skeet811fc892015-08-04 15:58:39 +0100109 /// <summary>
Jon Skeet72ec3362015-11-19 17:13:38 +0000110 /// Returns the accessor for this field.
Jon Skeet811fc892015-08-04 15:58:39 +0100111 /// </summary>
112 /// <remarks>
Jon Skeet72ec3362015-11-19 17:13:38 +0000113 /// <para>
Jon Skeet811fc892015-08-04 15:58:39 +0100114 /// 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.
Jon Skeet72ec3362015-11-19 17:13:38 +0000117 /// </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>
Jon Skeet811fc892015-08-04 15:58:39 +0100126 /// </remarks>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100127 public IFieldAccessor Accessor => accessor;
Jon Skeet9f37de92015-07-14 10:24:52 +0100128
129 /// <summary>
130 /// Maps a field type as included in the .proto file to a FieldType.
131 /// </summary>
132 private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
133 {
134 switch (type)
135 {
Jon Skeet84ea2c72016-04-08 12:33:09 +0100136 case FieldDescriptorProto.Types.Type.Double:
Jon Skeet9f37de92015-07-14 10:24:52 +0100137 return FieldType.Double;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100138 case FieldDescriptorProto.Types.Type.Float:
Jon Skeet9f37de92015-07-14 10:24:52 +0100139 return FieldType.Float;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100140 case FieldDescriptorProto.Types.Type.Int64:
Jon Skeet9f37de92015-07-14 10:24:52 +0100141 return FieldType.Int64;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100142 case FieldDescriptorProto.Types.Type.Uint64:
Jon Skeet9f37de92015-07-14 10:24:52 +0100143 return FieldType.UInt64;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100144 case FieldDescriptorProto.Types.Type.Int32:
Jon Skeet9f37de92015-07-14 10:24:52 +0100145 return FieldType.Int32;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100146 case FieldDescriptorProto.Types.Type.Fixed64:
Jon Skeet9f37de92015-07-14 10:24:52 +0100147 return FieldType.Fixed64;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100148 case FieldDescriptorProto.Types.Type.Fixed32:
Jon Skeet9f37de92015-07-14 10:24:52 +0100149 return FieldType.Fixed32;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100150 case FieldDescriptorProto.Types.Type.Bool:
Jon Skeet9f37de92015-07-14 10:24:52 +0100151 return FieldType.Bool;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100152 case FieldDescriptorProto.Types.Type.String:
Jon Skeet9f37de92015-07-14 10:24:52 +0100153 return FieldType.String;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100154 case FieldDescriptorProto.Types.Type.Group:
Jon Skeet9f37de92015-07-14 10:24:52 +0100155 return FieldType.Group;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100156 case FieldDescriptorProto.Types.Type.Message:
Jon Skeet9f37de92015-07-14 10:24:52 +0100157 return FieldType.Message;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100158 case FieldDescriptorProto.Types.Type.Bytes:
Jon Skeet9f37de92015-07-14 10:24:52 +0100159 return FieldType.Bytes;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100160 case FieldDescriptorProto.Types.Type.Uint32:
Jon Skeet9f37de92015-07-14 10:24:52 +0100161 return FieldType.UInt32;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100162 case FieldDescriptorProto.Types.Type.Enum:
Jon Skeet9f37de92015-07-14 10:24:52 +0100163 return FieldType.Enum;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100164 case FieldDescriptorProto.Types.Type.Sfixed32:
Jon Skeet9f37de92015-07-14 10:24:52 +0100165 return FieldType.SFixed32;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100166 case FieldDescriptorProto.Types.Type.Sfixed64:
Jon Skeet9f37de92015-07-14 10:24:52 +0100167 return FieldType.SFixed64;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100168 case FieldDescriptorProto.Types.Type.Sint32:
Jon Skeet9f37de92015-07-14 10:24:52 +0100169 return FieldType.SInt32;
Jon Skeet84ea2c72016-04-08 12:33:09 +0100170 case FieldDescriptorProto.Types.Type.Sint64:
Jon Skeet9f37de92015-07-14 10:24:52 +0100171 return FieldType.SInt64;
172 default:
173 throw new ArgumentException("Invalid type specified");
174 }
Jon Skeet811fc892015-08-04 15:58:39 +0100175 }
Jon Skeet9f37de92015-07-14 10:24:52 +0100176
Jon Skeet811fc892015-08-04 15:58:39 +0100177 /// <summary>
178 /// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise.
179 /// </summary>
Jon Skeet84ea2c72016-04-08 12:33:09 +0100180 public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
Jon Skeet9f37de92015-07-14 10:24:52 +0100181
Jon Skeet811fc892015-08-04 15:58:39 +0100182 /// <summary>
183 /// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
184 /// </summary>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100185 public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry;
Jon Skeet9f37de92015-07-14 10:24:52 +0100186
Jon Skeet811fc892015-08-04 15:58:39 +0100187 /// <summary>
188 /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise.
189 /// </summary>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100190 public bool IsPacked =>
Jon Skeet547d8e82015-08-07 13:37:21 +0100191 // 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.
Jon Skeet71e8dca2016-03-30 09:42:37 +0100195 Proto.Options == null || Proto.Options.Packed;
196
Jon Skeet811fc892015-08-04 15:58:39 +0100197 /// <summary>
Jon Skeetf5a0a7f2015-10-23 09:37:19 +0100198 /// Returns the type of the field.
Jon Skeet811fc892015-08-04 15:58:39 +0100199 /// </summary>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100200 public FieldType FieldType => fieldType;
Jon Skeet9f37de92015-07-14 10:24:52 +0100201
Jon Skeet811fc892015-08-04 15:58:39 +0100202 /// <summary>
203 /// Returns the field number declared in the proto file.
204 /// </summary>
Jon Skeet71e8dca2016-03-30 09:42:37 +0100205 public int FieldNumber => Proto.Number;
Jon Skeet9f37de92015-07-14 10:24:52 +0100206
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>
213 public int CompareTo(FieldDescriptor other)
214 {
Jon Skeet71e8dca2016-03-30 09:42:37 +0100215 if (other.ContainingType != ContainingType)
Jon Skeet9f37de92015-07-14 10:24:52 +0100216 {
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 {
Jon Skeet2212f562015-09-29 13:37:15 +0100247 throw new InvalidOperationException("MessageType is only valid for message fields.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100248 }
249 return messageType;
250 }
251 }
252
253 /// <summary>
Jon Skeet047575f2017-01-16 11:23:32 +0000254 /// The (possibly empty) set of custom options for this field.
255 /// </summary>
256 public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
257
258 /// <summary>
Jon Skeet9f37de92015-07-14 10:24:52 +0100259 /// Look up and cross-link all field types etc.
260 /// </summary>
261 internal void CrossLink()
262 {
263 if (Proto.TypeName != "")
264 {
265 IDescriptor typeDescriptor =
266 File.DescriptorPool.LookupSymbol(Proto.TypeName, this);
267
268 if (Proto.Type != 0)
269 {
270 // Choose field type based on symbol.
271 if (typeDescriptor is MessageDescriptor)
272 {
273 fieldType = FieldType.Message;
274 }
275 else if (typeDescriptor is EnumDescriptor)
276 {
277 fieldType = FieldType.Enum;
278 }
279 else
280 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000281 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100282 }
283 }
284
285 if (fieldType == FieldType.Message)
286 {
287 if (!(typeDescriptor is MessageDescriptor))
288 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000289 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100290 }
291 messageType = (MessageDescriptor) typeDescriptor;
292
293 if (Proto.DefaultValue != "")
294 {
295 throw new DescriptorValidationException(this, "Messages can't have default values.");
296 }
297 }
298 else if (fieldType == FieldType.Enum)
299 {
300 if (!(typeDescriptor is EnumDescriptor))
301 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000302 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100303 }
304 enumType = (EnumDescriptor) typeDescriptor;
305 }
306 else
307 {
308 throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
309 }
310 }
311 else
312 {
313 if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
314 {
315 throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
316 }
317 }
318
319 // Note: no attempt to perform any default value parsing
320
321 File.DescriptorPool.AddFieldByNumber(this);
322
Jon Skeet71e8dca2016-03-30 09:42:37 +0100323 if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
Jon Skeet9f37de92015-07-14 10:24:52 +0100324 {
325 throw new DescriptorValidationException(this, "MessageSet format is not supported.");
326 }
Jon Skeet71e8dca2016-03-30 09:42:37 +0100327 accessor = CreateAccessor();
Jon Skeet53c399a2015-07-20 19:24:31 +0100328 }
329
Jon Skeet71e8dca2016-03-30 09:42:37 +0100330 private IFieldAccessor CreateAccessor()
Jon Skeet53c399a2015-07-20 19:24:31 +0100331 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000332 // If we're given no property name, that's because we really don't want an accessor.
333 // (At the moment, that means it's a map entry message...)
334 if (propertyName == null)
Jon Skeet53c399a2015-07-20 19:24:31 +0100335 {
336 return null;
337 }
Jon Skeet71e8dca2016-03-30 09:42:37 +0100338 var property = ContainingType.ClrType.GetProperty(propertyName);
Jon Skeet53c399a2015-07-20 19:24:31 +0100339 if (property == null)
340 {
Jon Skeet71e8dca2016-03-30 09:42:37 +0100341 throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
Jon Skeet53c399a2015-07-20 19:24:31 +0100342 }
343 return IsMap ? new MapFieldAccessor(property, this)
344 : IsRepeated ? new RepeatedFieldAccessor(property, this)
345 : (IFieldAccessor) new SingleFieldAccessor(property, this);
Jon Skeet9f37de92015-07-14 10:24:52 +0100346 }
347 }
csharptest71f662c2011-05-20 15:15:34 -0500348}