DescriptorPool.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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. using System;
  33. using System.Collections.Generic;
  34. using System.Text;
  35. using System.Text.RegularExpressions;
  36. namespace Google.Protobuf.Reflection
  37. {
  38. /// <summary>
  39. /// Contains lookup tables containing all the descriptors defined in a particular file.
  40. /// </summary>
  41. internal sealed class DescriptorPool
  42. {
  43. private readonly IDictionary<string, IDescriptor> descriptorsByName =
  44. new Dictionary<string, IDescriptor>();
  45. private readonly IDictionary<DescriptorIntPair, FieldDescriptor> fieldsByNumber =
  46. new Dictionary<DescriptorIntPair, FieldDescriptor>();
  47. private readonly IDictionary<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber =
  48. new Dictionary<DescriptorIntPair, EnumValueDescriptor>();
  49. private readonly HashSet<FileDescriptor> dependencies;
  50. internal DescriptorPool(FileDescriptor[] dependencyFiles)
  51. {
  52. dependencies = new HashSet<FileDescriptor>();
  53. for (int i = 0; i < dependencyFiles.Length; i++)
  54. {
  55. dependencies.Add(dependencyFiles[i]);
  56. ImportPublicDependencies(dependencyFiles[i]);
  57. }
  58. foreach (FileDescriptor dependency in dependencyFiles)
  59. {
  60. AddPackage(dependency.Package, dependency);
  61. }
  62. }
  63. private void ImportPublicDependencies(FileDescriptor file)
  64. {
  65. foreach (FileDescriptor dependency in file.PublicDependencies)
  66. {
  67. if (dependencies.Add(dependency))
  68. {
  69. ImportPublicDependencies(dependency);
  70. }
  71. }
  72. }
  73. /// <summary>
  74. /// Finds a symbol of the given name within the pool.
  75. /// </summary>
  76. /// <typeparam name="T">The type of symbol to look for</typeparam>
  77. /// <param name="fullName">Fully-qualified name to look up</param>
  78. /// <returns>The symbol with the given name and type,
  79. /// or null if the symbol doesn't exist or has the wrong type</returns>
  80. internal T FindSymbol<T>(string fullName) where T : class
  81. {
  82. IDescriptor result;
  83. descriptorsByName.TryGetValue(fullName, out result);
  84. T descriptor = result as T;
  85. if (descriptor != null)
  86. {
  87. return descriptor;
  88. }
  89. // dependencies contains direct dependencies and any *public* dependencies
  90. // of those dependencies (transitively)... so we don't need to recurse here.
  91. foreach (FileDescriptor dependency in dependencies)
  92. {
  93. dependency.DescriptorPool.descriptorsByName.TryGetValue(fullName, out result);
  94. descriptor = result as T;
  95. if (descriptor != null)
  96. {
  97. return descriptor;
  98. }
  99. }
  100. return null;
  101. }
  102. /// <summary>
  103. /// Adds a package to the symbol tables. If a package by the same name
  104. /// already exists, that is fine, but if some other kind of symbol
  105. /// exists under the same name, an exception is thrown. If the package
  106. /// has multiple components, this also adds the parent package(s).
  107. /// </summary>
  108. internal void AddPackage(string fullName, FileDescriptor file)
  109. {
  110. int dotpos = fullName.LastIndexOf('.');
  111. String name;
  112. if (dotpos != -1)
  113. {
  114. AddPackage(fullName.Substring(0, dotpos), file);
  115. name = fullName.Substring(dotpos + 1);
  116. }
  117. else
  118. {
  119. name = fullName;
  120. }
  121. IDescriptor old;
  122. if (descriptorsByName.TryGetValue(fullName, out old))
  123. {
  124. if (!(old is PackageDescriptor))
  125. {
  126. throw new DescriptorValidationException(file,
  127. "\"" + name +
  128. "\" is already defined (as something other than a " +
  129. "package) in file \"" + old.File.Name + "\".");
  130. }
  131. }
  132. descriptorsByName[fullName] = new PackageDescriptor(name, fullName, file);
  133. }
  134. /// <summary>
  135. /// Adds a symbol to the symbol table.
  136. /// </summary>
  137. /// <exception cref="DescriptorValidationException">The symbol already existed
  138. /// in the symbol table.</exception>
  139. internal void AddSymbol(IDescriptor descriptor)
  140. {
  141. ValidateSymbolName(descriptor);
  142. String fullName = descriptor.FullName;
  143. IDescriptor old;
  144. if (descriptorsByName.TryGetValue(fullName, out old))
  145. {
  146. int dotPos = fullName.LastIndexOf('.');
  147. string message;
  148. if (descriptor.File == old.File)
  149. {
  150. if (dotPos == -1)
  151. {
  152. message = "\"" + fullName + "\" is already defined.";
  153. }
  154. else
  155. {
  156. message = "\"" + fullName.Substring(dotPos + 1) + "\" is already defined in \"" +
  157. fullName.Substring(0, dotPos) + "\".";
  158. }
  159. }
  160. else
  161. {
  162. message = "\"" + fullName + "\" is already defined in file \"" + old.File.Name + "\".";
  163. }
  164. throw new DescriptorValidationException(descriptor, message);
  165. }
  166. descriptorsByName[fullName] = descriptor;
  167. }
  168. private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$",
  169. FrameworkPortability.CompiledRegexWhereAvailable);
  170. /// <summary>
  171. /// Verifies that the descriptor's name is valid (i.e. it contains
  172. /// only letters, digits and underscores, and does not start with a digit).
  173. /// </summary>
  174. /// <param name="descriptor"></param>
  175. private static void ValidateSymbolName(IDescriptor descriptor)
  176. {
  177. if (descriptor.Name == "")
  178. {
  179. throw new DescriptorValidationException(descriptor, "Missing name.");
  180. }
  181. if (!ValidationRegex.IsMatch(descriptor.Name))
  182. {
  183. throw new DescriptorValidationException(descriptor,
  184. "\"" + descriptor.Name + "\" is not a valid identifier.");
  185. }
  186. }
  187. /// <summary>
  188. /// Returns the field with the given number in the given descriptor,
  189. /// or null if it can't be found.
  190. /// </summary>
  191. internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number)
  192. {
  193. FieldDescriptor ret;
  194. fieldsByNumber.TryGetValue(new DescriptorIntPair(messageDescriptor, number), out ret);
  195. return ret;
  196. }
  197. internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)
  198. {
  199. EnumValueDescriptor ret;
  200. enumValuesByNumber.TryGetValue(new DescriptorIntPair(enumDescriptor, number), out ret);
  201. return ret;
  202. }
  203. /// <summary>
  204. /// Adds a field to the fieldsByNumber table.
  205. /// </summary>
  206. /// <exception cref="DescriptorValidationException">A field with the same
  207. /// containing type and number already exists.</exception>
  208. internal void AddFieldByNumber(FieldDescriptor field)
  209. {
  210. DescriptorIntPair key = new DescriptorIntPair(field.ContainingType, field.FieldNumber);
  211. FieldDescriptor old;
  212. if (fieldsByNumber.TryGetValue(key, out old))
  213. {
  214. throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
  215. "has already been used in \"" +
  216. field.ContainingType.FullName +
  217. "\" by field \"" + old.Name + "\".");
  218. }
  219. fieldsByNumber[key] = field;
  220. }
  221. /// <summary>
  222. /// Adds an enum value to the enumValuesByNumber table. If an enum value
  223. /// with the same type and number already exists, this method does nothing.
  224. /// (This is allowed; the first value defined with the number takes precedence.)
  225. /// </summary>
  226. internal void AddEnumValueByNumber(EnumValueDescriptor enumValue)
  227. {
  228. DescriptorIntPair key = new DescriptorIntPair(enumValue.EnumDescriptor, enumValue.Number);
  229. if (!enumValuesByNumber.ContainsKey(key))
  230. {
  231. enumValuesByNumber[key] = enumValue;
  232. }
  233. }
  234. /// <summary>
  235. /// Looks up a descriptor by name, relative to some other descriptor.
  236. /// The name may be fully-qualified (with a leading '.'), partially-qualified,
  237. /// or unqualified. C++-like name lookup semantics are used to search for the
  238. /// matching descriptor.
  239. /// </summary>
  240. /// <remarks>
  241. /// This isn't heavily optimized, but it's only used during cross linking anyway.
  242. /// If it starts being used more widely, we should look at performance more carefully.
  243. /// </remarks>
  244. internal IDescriptor LookupSymbol(string name, IDescriptor relativeTo)
  245. {
  246. IDescriptor result;
  247. if (name.StartsWith("."))
  248. {
  249. // Fully-qualified name.
  250. result = FindSymbol<IDescriptor>(name.Substring(1));
  251. }
  252. else
  253. {
  254. // If "name" is a compound identifier, we want to search for the
  255. // first component of it, then search within it for the rest.
  256. int firstPartLength = name.IndexOf('.');
  257. string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength);
  258. // We will search each parent scope of "relativeTo" looking for the
  259. // symbol.
  260. StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName);
  261. while (true)
  262. {
  263. // Chop off the last component of the scope.
  264. int dotpos = scopeToTry.ToString().LastIndexOf(".");
  265. if (dotpos == -1)
  266. {
  267. result = FindSymbol<IDescriptor>(name);
  268. break;
  269. }
  270. else
  271. {
  272. scopeToTry.Length = dotpos + 1;
  273. // Append firstPart and try to find.
  274. scopeToTry.Append(firstPart);
  275. result = FindSymbol<IDescriptor>(scopeToTry.ToString());
  276. if (result != null)
  277. {
  278. if (firstPartLength != -1)
  279. {
  280. // We only found the first part of the symbol. Now look for
  281. // the whole thing. If this fails, we *don't* want to keep
  282. // searching parent scopes.
  283. scopeToTry.Length = dotpos + 1;
  284. scopeToTry.Append(name);
  285. result = FindSymbol<IDescriptor>(scopeToTry.ToString());
  286. }
  287. break;
  288. }
  289. // Not found. Remove the name so we can try again.
  290. scopeToTry.Length = dotpos;
  291. }
  292. }
  293. }
  294. if (result == null)
  295. {
  296. throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined.");
  297. }
  298. else
  299. {
  300. return result;
  301. }
  302. }
  303. /// <summary>
  304. /// Struct used to hold the keys for the fieldByNumber table.
  305. /// </summary>
  306. private struct DescriptorIntPair : IEquatable<DescriptorIntPair>
  307. {
  308. private readonly int number;
  309. private readonly IDescriptor descriptor;
  310. internal DescriptorIntPair(IDescriptor descriptor, int number)
  311. {
  312. this.number = number;
  313. this.descriptor = descriptor;
  314. }
  315. public bool Equals(DescriptorIntPair other)
  316. {
  317. return descriptor == other.descriptor
  318. && number == other.number;
  319. }
  320. public override bool Equals(object obj)
  321. {
  322. if (obj is DescriptorIntPair)
  323. {
  324. return Equals((DescriptorIntPair) obj);
  325. }
  326. return false;
  327. }
  328. public override int GetHashCode()
  329. {
  330. return descriptor.GetHashCode()*((1 << 16) - 1) + number;
  331. }
  332. }
  333. }
  334. }