blob: 45862affec1105b62e26a6753574542b0164bfe8 [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
33using System;
34using System.Collections.Generic;
35using System.Collections.ObjectModel;
Jon Skeet9f37de92015-07-14 10:24:52 +010036
37namespace Google.Protobuf.Reflection
38{
39 /// <summary>
40 /// Describes a .proto file, including everything defined within.
41 /// IDescriptor is implemented such that the File property returns this descriptor,
42 /// and the FullName is the same as the Name.
43 /// </summary>
44 public sealed class FileDescriptor : IDescriptor
45 {
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -070046 private readonly ByteString descriptorData;
Jon Skeet9f37de92015-07-14 10:24:52 +010047 private readonly FileDescriptorProto proto;
48 private readonly IList<MessageDescriptor> messageTypes;
49 private readonly IList<EnumDescriptor> enumTypes;
50 private readonly IList<ServiceDescriptor> services;
51 private readonly IList<FileDescriptor> dependencies;
52 private readonly IList<FileDescriptor> publicDependencies;
53 private readonly DescriptorPool pool;
Jon Skeet811fc892015-08-04 15:58:39 +010054
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -070055 private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedCodeInfo generatedCodeInfo)
Jon Skeet9f37de92015-07-14 10:24:52 +010056 {
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -070057 this.descriptorData = descriptorData;
Jon Skeet9f37de92015-07-14 10:24:52 +010058 this.pool = pool;
59 this.proto = proto;
60 this.dependencies = new ReadOnlyCollection<FileDescriptor>((FileDescriptor[]) dependencies.Clone());
61
62 publicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies);
63
64 pool.AddPackage(Package, this);
65
66 messageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType,
67 (message, index) =>
Jon Skeet4668c3d2015-07-22 11:38:22 +010068 new MessageDescriptor(message, this, null, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedTypes[index]));
Jon Skeet9f37de92015-07-14 10:24:52 +010069
70 enumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType,
71 (enumType, index) =>
Jon Skeet4668c3d2015-07-22 11:38:22 +010072 new EnumDescriptor(enumType, this, null, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedEnums[index]));
Jon Skeet9f37de92015-07-14 10:24:52 +010073
74 services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
75 (service, index) =>
76 new ServiceDescriptor(service, this, index));
77 }
78
79 /// <summary>
80 /// Computes the full name of a descriptor within this file, with an optional parent message.
81 /// </summary>
82 internal string ComputeFullName(MessageDescriptor parent, string name)
83 {
84 if (parent != null)
85 {
86 return parent.FullName + "." + name;
87 }
88 if (Package.Length > 0)
89 {
90 return Package + "." + name;
91 }
92 return name;
93 }
94
95 /// <summary>
96 /// Extracts public dependencies from direct dependencies. This is a static method despite its
97 /// first parameter, as the value we're in the middle of constructing is only used for exceptions.
98 /// </summary>
99 private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies)
100 {
101 var nameToFileMap = new Dictionary<string, FileDescriptor>();
102 foreach (var file in dependencies)
103 {
104 nameToFileMap[file.Name] = file;
105 }
106 var publicDependencies = new List<FileDescriptor>();
107 for (int i = 0; i < proto.PublicDependency.Count; i++)
108 {
109 int index = proto.PublicDependency[i];
110 if (index < 0 || index >= proto.Dependency.Count)
111 {
112 throw new DescriptorValidationException(@this, "Invalid public dependency index.");
113 }
114 string name = proto.Dependency[index];
115 FileDescriptor file = nameToFileMap[name];
116 if (file == null)
117 {
118 if (!allowUnknownDependencies)
119 {
120 throw new DescriptorValidationException(@this, "Invalid public dependency: " + name);
121 }
122 // Ignore unknown dependencies.
123 }
124 else
125 {
126 publicDependencies.Add(file);
127 }
128 }
129 return new ReadOnlyCollection<FileDescriptor>(publicDependencies);
130 }
131
132 /// <value>
133 /// The descriptor in its protocol message representation.
134 /// </value>
135 internal FileDescriptorProto Proto
136 {
137 get { return proto; }
138 }
139
140 /// <value>
141 /// The file name.
142 /// </value>
143 public string Name
144 {
145 get { return proto.Name; }
146 }
147
148 /// <summary>
149 /// The package as declared in the .proto file. This may or may not
150 /// be equivalent to the .NET namespace of the generated classes.
151 /// </summary>
152 public string Package
153 {
154 get { return proto.Package; }
155 }
156
157 /// <value>
158 /// Unmodifiable list of top-level message types declared in this file.
159 /// </value>
160 public IList<MessageDescriptor> MessageTypes
161 {
162 get { return messageTypes; }
163 }
164
165 /// <value>
166 /// Unmodifiable list of top-level enum types declared in this file.
167 /// </value>
168 public IList<EnumDescriptor> EnumTypes
169 {
170 get { return enumTypes; }
171 }
172
173 /// <value>
174 /// Unmodifiable list of top-level services declared in this file.
175 /// </value>
176 public IList<ServiceDescriptor> Services
177 {
178 get { return services; }
179 }
180
181 /// <value>
182 /// Unmodifiable list of this file's dependencies (imports).
183 /// </value>
184 public IList<FileDescriptor> Dependencies
185 {
186 get { return dependencies; }
187 }
188
189 /// <value>
190 /// Unmodifiable list of this file's public dependencies (public imports).
191 /// </value>
192 public IList<FileDescriptor> PublicDependencies
193 {
194 get { return publicDependencies; }
195 }
196
197 /// <value>
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700198 /// The original serialized binary form of this descriptor.
199 /// </value>
200 public ByteString SerializedData
201 {
202 get { return descriptorData; }
203 }
204
205 /// <value>
Jon Skeet9f37de92015-07-14 10:24:52 +0100206 /// Implementation of IDescriptor.FullName - just returns the same as Name.
207 /// </value>
208 string IDescriptor.FullName
209 {
210 get { return Name; }
211 }
212
213 /// <value>
214 /// Implementation of IDescriptor.File - just returns this descriptor.
215 /// </value>
216 FileDescriptor IDescriptor.File
217 {
218 get { return this; }
219 }
220
221 /// <value>
222 /// Pool containing symbol descriptors.
223 /// </value>
224 internal DescriptorPool DescriptorPool
225 {
226 get { return pool; }
227 }
228
229 /// <summary>
230 /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types.
231 /// </summary>
232 /// <param name="name">The unqualified type name to look for.</param>
Jon Skeet20bf6a52015-07-22 15:14:38 +0100233 /// <typeparam name="T">The type of descriptor to look for</typeparam>
Jon Skeet9f37de92015-07-14 10:24:52 +0100234 /// <returns>The type's descriptor, or null if not found.</returns>
235 public T FindTypeByName<T>(String name)
236 where T : class, IDescriptor
237 {
238 // Don't allow looking up nested types. This will make optimization
239 // easier later.
240 if (name.IndexOf('.') != -1)
241 {
242 return null;
243 }
244 if (Package.Length > 0)
245 {
246 name = Package + "." + name;
247 }
248 T result = pool.FindSymbol<T>(name);
249 if (result != null && result.File == this)
250 {
251 return result;
252 }
253 return null;
254 }
Jon Skeet4668c3d2015-07-22 11:38:22 +0100255
Jon Skeet9f37de92015-07-14 10:24:52 +0100256 /// <summary>
257 /// Builds a FileDescriptor from its protocol buffer representation.
258 /// </summary>
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700259 /// <param name="descriptorData">The original serialized descriptor data.
260 /// We have only limited proto2 support, so serializing FileDescriptorProto
261 /// would not necessarily give us this.</param>
Jon Skeet9f37de92015-07-14 10:24:52 +0100262 /// <param name="proto">The protocol message form of the FileDescriptor.</param>
263 /// <param name="dependencies">FileDescriptors corresponding to all of the
264 /// file's dependencies, in the exact order listed in the .proto file. May be null,
265 /// in which case it is treated as an empty array.</param>
266 /// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param>
Jon Skeet4668c3d2015-07-22 11:38:22 +0100267 /// <param name="generatedCodeInfo">Reflection information, if any. May be null, specifically for non-generated code.</param>
Jon Skeet9f37de92015-07-14 10:24:52 +0100268 /// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not
269 /// a valid descriptor. This can occur for a number of reasons, such as a field
270 /// having an undefined type or because two messages were defined with the same name.</exception>
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700271 private static FileDescriptor BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedCodeInfo generatedCodeInfo)
Jon Skeet9f37de92015-07-14 10:24:52 +0100272 {
273 // Building descriptors involves two steps: translating and linking.
274 // In the translation step (implemented by FileDescriptor's
275 // constructor), we build an object tree mirroring the
276 // FileDescriptorProto's tree and put all of the descriptors into the
277 // DescriptorPool's lookup tables. In the linking step, we look up all
278 // type references in the DescriptorPool, so that, for example, a
279 // FieldDescriptor for an embedded message contains a pointer directly
280 // to the Descriptor for that message's type. We also detect undefined
281 // types in the linking step.
282 if (dependencies == null)
283 {
284 dependencies = new FileDescriptor[0];
285 }
286
287 DescriptorPool pool = new DescriptorPool(dependencies);
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700288 FileDescriptor result = new FileDescriptor(descriptorData, proto, dependencies, pool, allowUnknownDependencies, generatedCodeInfo);
Jon Skeet9f37de92015-07-14 10:24:52 +0100289
Jon Skeet6f300442015-08-06 14:29:34 +0100290 // Validate that the dependencies we've been passed (as FileDescriptors) are actually the ones we
291 // need.
292 if (dependencies.Length != proto.Dependency.Count)
293 {
294 throw new DescriptorValidationException(result,
295 "Dependencies passed to FileDescriptor.BuildFrom() don't match " +
296 "those listed in the FileDescriptorProto.");
297 }
298 for (int i = 0; i < proto.Dependency.Count; i++)
299 {
300 if (dependencies[i].Name != proto.Dependency[i])
301 {
302 throw new DescriptorValidationException(result,
Jon Skeetca89a1a2015-08-25 14:32:28 +0100303 "Dependencies passed to FileDescriptor.BuildFrom() don't match " +
304 "those listed in the FileDescriptorProto. Expected: " +
305 proto.Dependency[i] + " but was: " + dependencies[i].Name);
Jon Skeet6f300442015-08-06 14:29:34 +0100306 }
307 }
Jon Skeet9f37de92015-07-14 10:24:52 +0100308
309 result.CrossLink();
310 return result;
311 }
312
313 private void CrossLink()
314 {
315 foreach (MessageDescriptor message in messageTypes)
316 {
317 message.CrossLink();
318 }
319
320 foreach (ServiceDescriptor service in services)
321 {
322 service.CrossLink();
323 }
324 }
325
Jon Skeet53c399a2015-07-20 19:24:31 +0100326 /// <summary>
327 /// Creates an instance for generated code.
328 /// </summary>
329 /// <remarks>
Jon Skeet4668c3d2015-07-22 11:38:22 +0100330 /// The <paramref name="generatedCodeInfo"/> parameter should be null for descriptors which don't correspond to
331 /// generated types. Otherwise, it should be a <see cref="GeneratedCodeInfo"/> with nested types and nested
332 /// enums corresponding to the types and enums contained within the file descriptor.
Jon Skeet53c399a2015-07-20 19:24:31 +0100333 /// </remarks>
Jon Skeet9f37de92015-07-14 10:24:52 +0100334 public static FileDescriptor InternalBuildGeneratedFileFrom(byte[] descriptorData,
Jon Skeet53c399a2015-07-20 19:24:31 +0100335 FileDescriptor[] dependencies,
Jon Skeet4668c3d2015-07-22 11:38:22 +0100336 GeneratedCodeInfo generatedCodeInfo)
Jon Skeet9f37de92015-07-14 10:24:52 +0100337 {
338 FileDescriptorProto proto;
339 try
340 {
341 proto = FileDescriptorProto.Parser.ParseFrom(descriptorData);
342 }
343 catch (InvalidProtocolBufferException e)
344 {
345 throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
346 }
347
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700348
349
Jon Skeet9f37de92015-07-14 10:24:52 +0100350 try
351 {
352 // When building descriptors for generated code, we allow unknown
353 // dependencies by default.
Jan Tattermusch3b8c83e2015-07-24 20:27:35 -0700354 return BuildFrom(ByteString.CopyFrom(descriptorData), proto, dependencies, true, generatedCodeInfo);
Jon Skeet9f37de92015-07-14 10:24:52 +0100355 }
356 catch (DescriptorValidationException e)
357 {
358 throw new ArgumentException("Invalid embedded descriptor for \"" + proto.Name + "\".", e);
359 }
360 }
Jon Skeet811fc892015-08-04 15:58:39 +0100361
362 /// <summary>
363 /// Returns a <see cref="System.String" /> that represents this instance.
364 /// </summary>
365 /// <returns>
366 /// A <see cref="System.String" /> that represents this instance.
367 /// </returns>
Jon Skeet9f37de92015-07-14 10:24:52 +0100368 public override string ToString()
369 {
370 return "FileDescriptor for " + proto.Name;
371 }
Jon Skeeta39abab2015-08-13 12:01:41 +0100372
373 /// <summary>
374 /// Returns the file descriptor for descriptor.proto.
375 /// </summary>
376 /// <remarks>
377 /// This is used for protos which take a direct dependency on <c>descriptor.proto</c>, typically for
378 /// annotations. While <c>descriptor.proto</c> is a proto2 file, it is built into the Google.Protobuf
379 /// runtime for reflection purposes. The messages are internal to the runtime as they would require
380 /// proto2 semantics for full support, but the file descriptor is available via this property. The
381 /// C# codegen in protoc automatically uses this property when it detects a dependency on <c>descriptor.proto</c>.
382 /// </remarks>
383 /// <value>
384 /// The file descriptor for <c>descriptor.proto</c>.
385 /// </value>
386 public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorProtoFile.Descriptor; } }
Jon Skeet9f37de92015-07-14 10:24:52 +0100387 }
Jon Skeet57599ef2010-11-03 11:21:52 +0000388}