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
33using System;
34using System.Collections.Generic;
35using System.Collections.ObjectModel;
36using System.Linq;
37using System.Reflection;
38#if NET35
39// Needed for ReadOnlyDictionary, which does not exist in .NET 3.5
40using Google.Protobuf.Collections;
41#endif
42
43namespace Google.Protobuf.Reflection
44{
45    /// <summary>
46    /// Describes a message type.
47    /// </summary>
48    public sealed class MessageDescriptor : DescriptorBase
49    {
50        private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
51        {
52            "google/protobuf/any.proto",
53            "google/protobuf/api.proto",
54            "google/protobuf/duration.proto",
55            "google/protobuf/empty.proto",
56            "google/protobuf/wrappers.proto",
57            "google/protobuf/timestamp.proto",
58            "google/protobuf/field_mask.proto",
59            "google/protobuf/source_context.proto",
60            "google/protobuf/struct.proto",
61            "google/protobuf/type.proto",
62        };
63
64        private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
65        private readonly IList<FieldDescriptor> fieldsInNumberOrder;
66        private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
67        private Func<IMessage, bool> extensionSetIsInitialized;
68
69        internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
70            : base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
71        {
72            Proto = proto;
73            Parser = generatedCodeInfo?.Parser;
74            ClrType = generatedCodeInfo?.ClrType;
75            ContainingType = parent;
76
77            // If generatedCodeInfo is null, we just won't generate an accessor for any fields.
78            Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
79                proto.OneofDecl,
80                (oneof, index) =>
81                new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index]));
82
83            int syntheticOneofCount = 0;
84            foreach (var oneof in Oneofs)
85            {
86                if (oneof.IsSynthetic)
87                {
88                    syntheticOneofCount++;
89                }
90                else if (syntheticOneofCount != 0)
91                {
92                    throw new ArgumentException("All synthetic oneofs should come after real oneofs");
93                }
94            }
95            RealOneofCount = Oneofs.Count - syntheticOneofCount;
96
97            NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
98                proto.NestedType,
99                (type, index) =>
100                new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index]));
101
102            EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
103                proto.EnumType,
104                (type, index) =>
105                new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index]));
106
107            Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
108
109            fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
110                proto.Field,
111                (field, index) =>
112                new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index], null));
113            fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
114            // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
115            jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder);
116            file.DescriptorPool.AddSymbol(this);
117            Fields = new FieldCollection(this);
118        }
119
120        private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
121        {
122            var map = new Dictionary<string, FieldDescriptor>();
123            foreach (var field in fields)
124            {
125                map[field.Name] = field;
126                map[field.JsonName] = field;
127            }
128            return new ReadOnlyDictionary<string, FieldDescriptor>(map);
129        }
130
131        /// <summary>
132        /// The brief name of the descriptor's target.
133        /// </summary>
134        public override string Name => Proto.Name;
135
136        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
137        {
138            switch (fieldNumber)
139            {
140                case DescriptorProto.FieldFieldNumber:
141                    return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
142                case DescriptorProto.NestedTypeFieldNumber:
143                    return (IReadOnlyList<DescriptorBase>) NestedTypes;
144                case DescriptorProto.EnumTypeFieldNumber:
145                    return (IReadOnlyList<DescriptorBase>) EnumTypes;
146                default:
147                    return null;
148            }
149        }
150
151        internal DescriptorProto Proto { get; }
152
153        internal bool IsExtensionsInitialized(IMessage message)
154        {
155            if (Proto.ExtensionRange.Count == 0)
156            {
157                return true;
158            }
159
160            if (extensionSetIsInitialized == null)
161            {
162                extensionSetIsInitialized = ReflectionUtil.CreateIsInitializedCaller(ClrType);
163            }
164
165            return extensionSetIsInitialized(message);
166        }
167
168        /// <summary>
169        /// The CLR type used to represent message instances from this descriptor.
170        /// </summary>
171        /// <remarks>
172        /// <para>
173        /// The value returned by this property will be non-null for all regular fields. However,
174        /// if a message containing a map field is introspected, the list of nested messages will include
175        /// an auto-generated nested key/value pair message for the field. This is not represented in any
176        /// generated type, so this property will return null in such cases.
177        /// </para>
178        /// <para>
179        /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
180        /// will be the generated message type, not the native type used by reflection for fields of those types. Code
181        /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
182        /// a wrapper type, and handle the result appropriately.
183        /// </para>
184        /// </remarks>
185        public Type ClrType { get; }
186
187        /// <summary>
188        /// A parser for this message type.
189        /// </summary>
190        /// <remarks>
191        /// <para>
192        /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
193        /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
194        /// </para>
195        /// <para>
196        /// The value returned by this property will be non-null for all regular fields. However,
197        /// if a message containing a map field is introspected, the list of nested messages will include
198        /// an auto-generated nested key/value pair message for the field. No message parser object is created for
199        /// such messages, so this property will return null in such cases.
200        /// </para>
201        /// <para>
202        /// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
203        /// will be the generated message type, not the native type used by reflection for fields of those types. Code
204        /// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
205        /// a wrapper type, and handle the result appropriately.
206        /// </para>
207        /// </remarks>
208        public MessageParser Parser { get; }
209
210        /// <summary>
211        /// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
212        /// </summary>
213        internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
214
215        /// <summary>
216        /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
217        /// with the addition of presence.
218        /// </summary>
219        internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
220
221        /// <value>
222        /// If this is a nested type, get the outer descriptor, otherwise null.
223        /// </value>
224        public MessageDescriptor ContainingType { get; }
225
226        /// <value>
227        /// A collection of fields, which can be retrieved by name or field number.
228        /// </value>
229        public FieldCollection Fields { get; }
230
231        /// <summary>
232        /// An unmodifiable list of extensions defined in this message's scope.
233        /// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
234        /// if they are declared in a file generated using a version of protoc that did not fully
235        /// support extensions in C#.
236        /// </summary>
237        public ExtensionCollection Extensions { get; }
238
239        /// <value>
240        /// An unmodifiable list of this message type's nested types.
241        /// </value>
242        public IList<MessageDescriptor> NestedTypes { get; }
243
244        /// <value>
245        /// An unmodifiable list of this message type's enum types.
246        /// </value>
247        public IList<EnumDescriptor> EnumTypes { get; }
248
249        /// <value>
250        /// An unmodifiable list of the "oneof" field collections in this message type.
251        /// All "real" oneofs (where <see cref="OneofDescriptor.IsSynthetic"/> returns false)
252        /// come before synthetic ones.
253        /// </value>
254        public IList<OneofDescriptor> Oneofs { get; }
255
256        /// <summary>
257        /// The number of real "oneof" descriptors in this message type. Every element in <see cref="Oneofs"/>
258        /// with an index less than this will have a <see cref="OneofDescriptor.IsSynthetic"/> property value
259        /// of <c>false</c>; every element with an index greater than or equal to this will have a
260        /// <see cref="OneofDescriptor.IsSynthetic"/> property value of <c>true</c>.
261        /// </summary>
262        public int RealOneofCount { get; }
263
264        /// <summary>
265        /// Finds a field by field name.
266        /// </summary>
267        /// <param name="name">The unqualified name of the field (e.g. "foo").</param>
268        /// <returns>The field's descriptor, or null if not found.</returns>
269        public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
270
271        /// <summary>
272        /// Finds a field by field number.
273        /// </summary>
274        /// <param name="number">The field number within this message type.</param>
275        /// <returns>The field's descriptor, or null if not found.</returns>
276        public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
277
278        /// <summary>
279        /// Finds a nested descriptor by name. The is valid for fields, nested
280        /// message types, oneofs and enums.
281        /// </summary>
282        /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
283        /// <returns>The descriptor, or null if not found.</returns>
284        public T FindDescriptor<T>(string name)  where T : class, IDescriptor =>
285            File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
286
287        /// <summary>
288        /// The (possibly empty) set of custom options for this message.
289        /// </summary>
290        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
291        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
292
293        /// <summary>
294        /// The <c>MessageOptions</c>, defined in <c>descriptor.proto</c>.
295        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
296        /// Custom options can be retrieved as extensions of the returned message.
297        /// NOTE: A defensive copy is created each time this property is retrieved.
298        /// </summary>
299        public MessageOptions GetOptions() => Proto.Options?.Clone();
300
301        /// <summary>
302        /// Gets a single value message option for this descriptor
303        /// </summary>
304        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
305        public T GetOption<T>(Extension<MessageOptions, T> extension)
306        {
307            var value = Proto.Options.GetExtension(extension);
308            return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
309        }
310
311        /// <summary>
312        /// Gets a repeated value message option for this descriptor
313        /// </summary>
314        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
315        public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension)
316        {
317            return Proto.Options.GetExtension(extension).Clone();
318        }
319
320        /// <summary>
321        /// Looks up and cross-links all fields and nested types.
322        /// </summary>
323        internal void CrossLink()
324        {
325            foreach (MessageDescriptor message in NestedTypes)
326            {
327                message.CrossLink();
328            }
329
330            foreach (FieldDescriptor field in fieldsInDeclarationOrder)
331            {
332                field.CrossLink();
333            }
334
335            foreach (OneofDescriptor oneof in Oneofs)
336            {
337                oneof.CrossLink();
338            }
339
340            Extensions.CrossLink();
341        }
342
343        /// <summary>
344        /// A collection to simplify retrieving the field accessor for a particular field.
345        /// </summary>
346        public sealed class FieldCollection
347        {
348            private readonly MessageDescriptor messageDescriptor;
349
350            internal FieldCollection(MessageDescriptor messageDescriptor)
351            {
352                this.messageDescriptor = messageDescriptor;
353            }
354
355            /// <value>
356            /// Returns the fields in the message as an immutable list, in the order in which they
357            /// are declared in the source .proto file.
358            /// </value>
359            public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
360
361            /// <value>
362            /// Returns the fields in the message as an immutable list, in ascending field number
363            /// order. Field numbers need not be contiguous, so there is no direct mapping from the
364            /// index in the list to the field number; to retrieve a field by field number, it is better
365            /// to use the <see cref="FieldCollection"/> indexer.
366            /// </value>
367            public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
368
369            // TODO: consider making this public in the future. (Being conservative for now...)
370
371            /// <value>
372            /// Returns a read-only dictionary mapping the field names in this message as they're available
373            /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
374            /// in the message would result two entries, one with a key <c>fooBar</c> and one with a key
375            /// <c>foo_bar</c>, both referring to the same field.
376            /// </value>
377            internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
378
379            /// <summary>
380            /// Retrieves the descriptor for the field with the given number.
381            /// </summary>
382            /// <param name="number">Number of the field to retrieve the descriptor for</param>
383            /// <returns>The accessor for the given field</returns>
384            /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
385            /// with the given number</exception>
386            public FieldDescriptor this[int number]
387            {
388                get
389                {
390                    var fieldDescriptor = messageDescriptor.FindFieldByNumber(number);
391                    if (fieldDescriptor == null)
392                    {
393                        throw new KeyNotFoundException("No such field number");
394                    }
395                    return fieldDescriptor;
396                }
397            }
398
399            /// <summary>
400            /// Retrieves the descriptor for the field with the given name.
401            /// </summary>
402            /// <param name="name">Name of the field to retrieve the descriptor for</param>
403            /// <returns>The descriptor for the given field</returns>
404            /// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
405            /// with the given name</exception>
406            public FieldDescriptor this[string name]
407            {
408                get
409                {
410                    var fieldDescriptor = messageDescriptor.FindFieldByName(name);
411                    if (fieldDescriptor == null)
412                    {
413                        throw new KeyNotFoundException("No such field name");
414                    }
415                    return fieldDescriptor;
416                }
417            }
418        }
419    }
420}
421