1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 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 using System.Collections.Generic;
33 using System.Linq;
34 
35 namespace Google.Protobuf.Reflection
36 {
37     /// <summary>
38     /// An immutable registry of types which can be looked up by their full name.
39     /// </summary>
40     public sealed class TypeRegistry
41     {
42         /// <summary>
43         /// An empty type registry, containing no types.
44         /// </summary>
45         public static TypeRegistry Empty { get; } = new TypeRegistry(new Dictionary<string, MessageDescriptor>());
46 
47         private readonly Dictionary<string, MessageDescriptor> fullNameToMessageMap;
48 
TypeRegistry(Dictionary<string, MessageDescriptor> fullNameToMessageMap)49         private TypeRegistry(Dictionary<string, MessageDescriptor> fullNameToMessageMap)
50         {
51             this.fullNameToMessageMap = fullNameToMessageMap;
52         }
53 
54         /// <summary>
55         /// Attempts to find a message descriptor by its full name.
56         /// </summary>
57         /// <param name="fullName">The full name of the message, which is the dot-separated
58         /// combination of package, containing messages and message name</param>
59         /// <returns>The message descriptor corresponding to <paramref name="fullName"/> or null
60         /// if there is no such message descriptor.</returns>
Find(string fullName)61         public MessageDescriptor Find(string fullName)
62         {
63             MessageDescriptor ret;
64             // Ignore the return value as ret will end up with the right value either way.
65             fullNameToMessageMap.TryGetValue(fullName, out ret);
66             return ret;
67         }
68 
69         /// <summary>
70         /// Creates a type registry from the specified set of file descriptors.
71         /// </summary>
72         /// <remarks>
73         /// This is a convenience overload for <see cref="FromFiles(IEnumerable{FileDescriptor})"/>
74         /// to allow calls such as <c>TypeRegistry.FromFiles(descriptor1, descriptor2)</c>.
75         /// </remarks>
76         /// <param name="fileDescriptors">The set of files to include in the registry. Must not contain null values.</param>
77         /// <returns>A type registry for the given files.</returns>
FromFiles(params FileDescriptor[] fileDescriptors)78         public static TypeRegistry FromFiles(params FileDescriptor[] fileDescriptors)
79         {
80             return FromFiles((IEnumerable<FileDescriptor>) fileDescriptors);
81         }
82 
83         /// <summary>
84         /// Creates a type registry from the specified set of file descriptors.
85         /// </summary>
86         /// <remarks>
87         /// All message types within all the specified files are added to the registry, and
88         /// the dependencies of the specified files are also added, recursively.
89         /// </remarks>
90         /// <param name="fileDescriptors">The set of files to include in the registry. Must not contain null values.</param>
91         /// <returns>A type registry for the given files.</returns>
FromFiles(IEnumerable<FileDescriptor> fileDescriptors)92         public static TypeRegistry FromFiles(IEnumerable<FileDescriptor> fileDescriptors)
93         {
94             ProtoPreconditions.CheckNotNull(fileDescriptors, nameof(fileDescriptors));
95             var builder = new Builder();
96             foreach (var file in fileDescriptors)
97             {
98                 builder.AddFile(file);
99             }
100             return builder.Build();
101         }
102 
103         /// <summary>
104         /// Creates a type registry from the file descriptor parents of the specified set of message descriptors.
105         /// </summary>
106         /// <remarks>
107         /// This is a convenience overload for <see cref="FromMessages(IEnumerable{MessageDescriptor})"/>
108         /// to allow calls such as <c>TypeRegistry.FromFiles(descriptor1, descriptor2)</c>.
109         /// </remarks>
110         /// <param name="messageDescriptors">The set of message descriptors to use to identify file descriptors to include in the registry.
111         /// Must not contain null values.</param>
112         /// <returns>A type registry for the given files.</returns>
FromMessages(params MessageDescriptor[] messageDescriptors)113         public static TypeRegistry FromMessages(params MessageDescriptor[] messageDescriptors)
114         {
115             return FromMessages((IEnumerable<MessageDescriptor>) messageDescriptors);
116         }
117 
118         /// <summary>
119         /// Creates a type registry from the file descriptor parents of the specified set of message descriptors.
120         /// </summary>
121         /// <remarks>
122         /// The specified message descriptors are only used to identify their file descriptors; the returned registry
123         /// contains all the types within the file descriptors which contain the specified message descriptors (and
124         /// the dependencies of those files), not just the specified messages.
125         /// </remarks>
126         /// <param name="messageDescriptors">The set of message descriptors to use to identify file descriptors to include in the registry.
127         /// Must not contain null values.</param>
128         /// <returns>A type registry for the given files.</returns>
FromMessages(IEnumerable<MessageDescriptor> messageDescriptors)129         public static TypeRegistry FromMessages(IEnumerable<MessageDescriptor> messageDescriptors)
130         {
131             ProtoPreconditions.CheckNotNull(messageDescriptors, nameof(messageDescriptors));
132             return FromFiles(messageDescriptors.Select(md => md.File));
133         }
134 
135         /// <summary>
136         /// Builder class which isn't exposed, but acts as a convenient alternative to passing round two dictionaries in recursive calls.
137         /// </summary>
138         private class Builder
139         {
140             private readonly Dictionary<string, MessageDescriptor> types;
141             private readonly HashSet<string> fileDescriptorNames;
142 
Builder()143             internal Builder()
144             {
145                 types = new Dictionary<string, MessageDescriptor>();
146                 fileDescriptorNames = new HashSet<string>();
147             }
148 
AddFile(FileDescriptor fileDescriptor)149             internal void AddFile(FileDescriptor fileDescriptor)
150             {
151                 if (!fileDescriptorNames.Add(fileDescriptor.Name))
152                 {
153                     return;
154                 }
155                 foreach (var dependency in fileDescriptor.Dependencies)
156                 {
157                     AddFile(dependency);
158                 }
159                 foreach (var message in fileDescriptor.MessageTypes)
160                 {
161                     AddMessage(message);
162                 }
163             }
164 
AddMessage(MessageDescriptor messageDescriptor)165             private void AddMessage(MessageDescriptor messageDescriptor)
166             {
167                 foreach (var nestedType in messageDescriptor.NestedTypes)
168                 {
169                     AddMessage(nestedType);
170                 }
171                 // This will overwrite any previous entry. Given that each file should
172                 // only be added once, this could be a problem such as package A.B with type C,
173                 // and package A with type B.C... it's unclear what we should do in that case.
174                 types[messageDescriptor.FullName] = messageDescriptor;
175             }
176 
Build()177             internal TypeRegistry Build()
178             {
179                 return new TypeRegistry(types);
180             }
181         }
182     }
183 }
184