blob: 86942acc02250b1c7a1c1b1558bc3979266ff8cb [file] [log] [blame]
Brian Silverman9c614bc2016-02-15 20:20:02 -05001#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
33using System;
34using System.Collections.Generic;
35using System.Collections.ObjectModel;
36using System.Linq;
Austin Schuh40c16522018-10-28 20:27:54 -070037#if NET35
38// Needed for ReadOnlyDictionary, which does not exist in .NET 3.5
39using Google.Protobuf.Collections;
40#endif
Brian Silverman9c614bc2016-02-15 20:20:02 -050041
42namespace Google.Protobuf.Reflection
43{
44 /// <summary>
45 /// Describes a message type.
46 /// </summary>
47 public sealed class MessageDescriptor : DescriptorBase
48 {
49 private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
50 {
51 "google/protobuf/any.proto",
52 "google/protobuf/api.proto",
53 "google/protobuf/duration.proto",
54 "google/protobuf/empty.proto",
55 "google/protobuf/wrappers.proto",
56 "google/protobuf/timestamp.proto",
57 "google/protobuf/field_mask.proto",
58 "google/protobuf/source_context.proto",
59 "google/protobuf/struct.proto",
60 "google/protobuf/type.proto",
61 };
62
63 private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
64 private readonly IList<FieldDescriptor> fieldsInNumberOrder;
65 private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
66
67 internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
68 : base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
69 {
70 Proto = proto;
71 Parser = generatedCodeInfo?.Parser;
72 ClrType = generatedCodeInfo?.ClrType;
73 ContainingType = parent;
74
75 // Note use of generatedCodeInfo. rather than generatedCodeInfo?. here... we don't expect
76 // to see any nested oneofs, types or enums in "not actually generated" code... we do
77 // expect fields though (for map entry messages).
78 Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
79 proto.OneofDecl,
80 (oneof, index) =>
81 new OneofDescriptor(oneof, file, this, index, generatedCodeInfo.OneofNames[index]));
82
83 NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
84 proto.NestedType,
85 (type, index) =>
86 new MessageDescriptor(type, file, this, index, generatedCodeInfo.NestedTypes[index]));
87
88 EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
89 proto.EnumType,
90 (type, index) =>
91 new EnumDescriptor(type, file, this, index, generatedCodeInfo.NestedEnums[index]));
92
93 fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
94 proto.Field,
95 (field, index) =>
96 new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index]));
97 fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
98 // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
99 jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder);
100 file.DescriptorPool.AddSymbol(this);
101 Fields = new FieldCollection(this);
102 }
103
104 private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
105 {
106 var map = new Dictionary<string, FieldDescriptor>();
107 foreach (var field in fields)
108 {
Brian Silverman9c614bc2016-02-15 20:20:02 -0500109 map[field.Name] = field;
Austin Schuh40c16522018-10-28 20:27:54 -0700110 map[field.JsonName] = field;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500111 }
112 return new ReadOnlyDictionary<string, FieldDescriptor>(map);
113 }
114
115 /// <summary>
116 /// The brief name of the descriptor's target.
117 /// </summary>
118 public override string Name => Proto.Name;
119
120 internal DescriptorProto Proto { get; }
121
122 /// <summary>
123 /// The CLR type used to represent message instances from this descriptor.
124 /// </summary>
125 /// <remarks>
126 /// <para>
127 /// The value returned by this property will be non-null for all regular fields. However,
128 /// if a message containing a map field is introspected, the list of nested messages will include
129 /// an auto-generated nested key/value pair message for the field. This is not represented in any
130 /// generated type, so this property will return null in such cases.
131 /// </para>
132 /// <para>
133 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
134 /// will be the generated message type, not the native type used by reflection for fields of those types. Code
135 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
136 /// a wrapper type, and handle the result appropriately.
137 /// </para>
138 /// </remarks>
139 public Type ClrType { get; }
140
141 /// <summary>
142 /// A parser for this message type.
143 /// </summary>
144 /// <remarks>
145 /// <para>
146 /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
147 /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
148 /// </para>
149 /// <para>
150 /// The value returned by this property will be non-null for all regular fields. However,
151 /// if a message containing a map field is introspected, the list of nested messages will include
152 /// an auto-generated nested key/value pair message for the field. No message parser object is created for
153 /// such messages, so this property will return null in such cases.
154 /// </para>
155 /// <para>
156 /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
157 /// will be the generated message type, not the native type used by reflection for fields of those types. Code
158 /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
159 /// a wrapper type, and handle the result appropriately.
160 /// </para>
161 /// </remarks>
162 public MessageParser Parser { get; }
163
164 /// <summary>
165 /// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
166 /// </summary>
167 internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
168
169 /// <summary>
170 /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
171 /// with the addition of presence.
172 /// </summary>
173 internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
174
175 /// <value>
176 /// If this is a nested type, get the outer descriptor, otherwise null.
177 /// </value>
178 public MessageDescriptor ContainingType { get; }
179
180 /// <value>
181 /// A collection of fields, which can be retrieved by name or field number.
182 /// </value>
183 public FieldCollection Fields { get; }
184
185 /// <value>
186 /// An unmodifiable list of this message type's nested types.
187 /// </value>
188 public IList<MessageDescriptor> NestedTypes { get; }
189
190 /// <value>
191 /// An unmodifiable list of this message type's enum types.
192 /// </value>
193 public IList<EnumDescriptor> EnumTypes { get; }
194
195 /// <value>
196 /// An unmodifiable list of the "oneof" field collections in this message type.
197 /// </value>
198 public IList<OneofDescriptor> Oneofs { get; }
199
200 /// <summary>
201 /// Finds a field by field name.
202 /// </summary>
203 /// <param name="name">The unqualified name of the field (e.g. "foo").</param>
204 /// <returns>The field's descriptor, or null if not found.</returns>
205 public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
206
207 /// <summary>
208 /// Finds a field by field number.
209 /// </summary>
210 /// <param name="number">The field number within this message type.</param>
211 /// <returns>The field's descriptor, or null if not found.</returns>
212 public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
213
214 /// <summary>
215 /// Finds a nested descriptor by name. The is valid for fields, nested
216 /// message types, oneofs and enums.
217 /// </summary>
218 /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
219 /// <returns>The descriptor, or null if not found.</returns>
220 public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
221 File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
222
223 /// <summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700224 /// The (possibly empty) set of custom options for this message.
225 /// </summary>
226 public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
227
228 /// <summary>
Brian Silverman9c614bc2016-02-15 20:20:02 -0500229 /// Looks up and cross-links all fields and nested types.
230 /// </summary>
231 internal void CrossLink()
232 {
233 foreach (MessageDescriptor message in NestedTypes)
234 {
235 message.CrossLink();
236 }
237
238 foreach (FieldDescriptor field in fieldsInDeclarationOrder)
239 {
240 field.CrossLink();
241 }
242
243 foreach (OneofDescriptor oneof in Oneofs)
244 {
245 oneof.CrossLink();
246 }
247 }
248
249 /// <summary>
250 /// A collection to simplify retrieving the field accessor for a particular field.
251 /// </summary>
252 public sealed class FieldCollection
253 {
254 private readonly MessageDescriptor messageDescriptor;
255
256 internal FieldCollection(MessageDescriptor messageDescriptor)
257 {
258 this.messageDescriptor = messageDescriptor;
259 }
260
261 /// <value>
262 /// Returns the fields in the message as an immutable list, in the order in which they
263 /// are declared in the source .proto file.
264 /// </value>
265 public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
266
267 /// <value>
268 /// Returns the fields in the message as an immutable list, in ascending field number
269 /// order. Field numbers need not be contiguous, so there is no direct mapping from the
270 /// index in the list to the field number; to retrieve a field by field number, it is better
271 /// to use the <see cref="FieldCollection"/> indexer.
272 /// </value>
273 public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
274
275 // TODO: consider making this public in the future. (Being conservative for now...)
276
277 /// <value>
278 /// Returns a read-only dictionary mapping the field names in this message as they're available
279 /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
280 /// in the message would result two entries, one with a key <c>fooBar</c> and one with a key
281 /// <c>foo_bar</c>, both referring to the same field.
282 /// </value>
283 internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
284
285 /// <summary>
286 /// Retrieves the descriptor for the field with the given number.
287 /// </summary>
288 /// <param name="number">Number of the field to retrieve the descriptor for</param>
289 /// <returns>The accessor for the given field</returns>
290 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
291 /// with the given number</exception>
292 public FieldDescriptor this[int number]
293 {
294 get
295 {
296 var fieldDescriptor = messageDescriptor.FindFieldByNumber(number);
297 if (fieldDescriptor == null)
298 {
299 throw new KeyNotFoundException("No such field number");
300 }
301 return fieldDescriptor;
302 }
303 }
304
305 /// <summary>
306 /// Retrieves the descriptor for the field with the given name.
307 /// </summary>
308 /// <param name="name">Name of the field to retrieve the descriptor for</param>
309 /// <returns>The descriptor for the given field</returns>
310 /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
311 /// with the given name</exception>
312 public FieldDescriptor this[string name]
313 {
314 get
315 {
316 var fieldDescriptor = messageDescriptor.FindFieldByName(name);
317 if (fieldDescriptor == null)
318 {
319 throw new KeyNotFoundException("No such field name");
320 }
321 return fieldDescriptor;
322 }
323 }
324 }
325 }
326}