blob: de6e57176d6d04ee9636c189f1fe452d93944031 [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 Skeet71e8dca2016-03-30 09:42:37 +0100100 JsonName = Proto.JsonName == "" ? JsonFormatter.ToCamelCase(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 {
136 case FieldDescriptorProto.Types.Type.TYPE_DOUBLE:
137 return FieldType.Double;
138 case FieldDescriptorProto.Types.Type.TYPE_FLOAT:
139 return FieldType.Float;
140 case FieldDescriptorProto.Types.Type.TYPE_INT64:
141 return FieldType.Int64;
142 case FieldDescriptorProto.Types.Type.TYPE_UINT64:
143 return FieldType.UInt64;
144 case FieldDescriptorProto.Types.Type.TYPE_INT32:
145 return FieldType.Int32;
146 case FieldDescriptorProto.Types.Type.TYPE_FIXED64:
147 return FieldType.Fixed64;
148 case FieldDescriptorProto.Types.Type.TYPE_FIXED32:
149 return FieldType.Fixed32;
150 case FieldDescriptorProto.Types.Type.TYPE_BOOL:
151 return FieldType.Bool;
152 case FieldDescriptorProto.Types.Type.TYPE_STRING:
153 return FieldType.String;
154 case FieldDescriptorProto.Types.Type.TYPE_GROUP:
155 return FieldType.Group;
156 case FieldDescriptorProto.Types.Type.TYPE_MESSAGE:
157 return FieldType.Message;
158 case FieldDescriptorProto.Types.Type.TYPE_BYTES:
159 return FieldType.Bytes;
160 case FieldDescriptorProto.Types.Type.TYPE_UINT32:
161 return FieldType.UInt32;
162 case FieldDescriptorProto.Types.Type.TYPE_ENUM:
163 return FieldType.Enum;
164 case FieldDescriptorProto.Types.Type.TYPE_SFIXED32:
165 return FieldType.SFixed32;
166 case FieldDescriptorProto.Types.Type.TYPE_SFIXED64:
167 return FieldType.SFixed64;
168 case FieldDescriptorProto.Types.Type.TYPE_SINT32:
169 return FieldType.SInt32;
170 case FieldDescriptorProto.Types.Type.TYPE_SINT64:
171 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 Skeet71e8dca2016-03-30 09:42:37 +0100180 public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.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>
254 /// Look up and cross-link all field types etc.
255 /// </summary>
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 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000276 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100277 }
278 }
279
280 if (fieldType == FieldType.Message)
281 {
282 if (!(typeDescriptor is MessageDescriptor))
283 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000284 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100285 }
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 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000297 throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
Jon Skeet9f37de92015-07-14 10:24:52 +0100298 }
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
Jon Skeet71e8dca2016-03-30 09:42:37 +0100318 if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
Jon Skeet9f37de92015-07-14 10:24:52 +0100319 {
320 throw new DescriptorValidationException(this, "MessageSet format is not supported.");
321 }
Jon Skeet71e8dca2016-03-30 09:42:37 +0100322 accessor = CreateAccessor();
Jon Skeet53c399a2015-07-20 19:24:31 +0100323 }
324
Jon Skeet71e8dca2016-03-30 09:42:37 +0100325 private IFieldAccessor CreateAccessor()
Jon Skeet53c399a2015-07-20 19:24:31 +0100326 {
Jon Skeet72ec3362015-11-19 17:13:38 +0000327 // 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)
Jon Skeet53c399a2015-07-20 19:24:31 +0100330 {
331 return null;
332 }
Jon Skeet71e8dca2016-03-30 09:42:37 +0100333 var property = ContainingType.ClrType.GetProperty(propertyName);
Jon Skeet53c399a2015-07-20 19:24:31 +0100334 if (property == null)
335 {
Jon Skeet71e8dca2016-03-30 09:42:37 +0100336 throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
Jon Skeet53c399a2015-07-20 19:24:31 +0100337 }
338 return IsMap ? new MapFieldAccessor(property, this)
339 : IsRepeated ? new RepeatedFieldAccessor(property, this)
340 : (IFieldAccessor) new SingleFieldAccessor(property, this);
Jon Skeet9f37de92015-07-14 10:24:52 +0100341 }
342 }
csharptest71f662c2011-05-20 15:15:34 -0500343}