DescriptorPool.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // Protocol Buffers - Google's data interchange format
  2. // Copyright 2008 Google Inc.
  3. // http://code.google.com/p/protobuf/
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. using System.Collections.Generic;
  17. using System;
  18. using System.Text;
  19. using System.Text.RegularExpressions;
  20. namespace Google.ProtocolBuffers.Descriptors {
  21. /// <summary>
  22. /// Contains lookup tables containing all the descriptors defined in a particular file.
  23. /// </summary>
  24. internal sealed class DescriptorPool {
  25. private readonly IDictionary<string, IDescriptor> descriptorsByName =
  26. new Dictionary<string, IDescriptor>();
  27. private readonly IDictionary<DescriptorIntPair, FieldDescriptor> fieldsByNumber =
  28. new Dictionary<DescriptorIntPair, FieldDescriptor>();
  29. private readonly IDictionary<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber =
  30. new Dictionary<DescriptorIntPair, EnumValueDescriptor>();
  31. private readonly DescriptorPool[] dependencies;
  32. internal DescriptorPool(FileDescriptor[] dependencyFiles) {
  33. dependencies = new DescriptorPool[dependencyFiles.Length];
  34. for (int i = 0; i < dependencyFiles.Length; i++) {
  35. dependencies[i] = dependencyFiles[i].DescriptorPool;
  36. }
  37. foreach (FileDescriptor dependency in dependencyFiles) {
  38. AddPackage(dependency.Package, dependency);
  39. }
  40. }
  41. /// <summary>
  42. /// Finds a symbol of the given name within the pool.
  43. /// </summary>
  44. /// <typeparam name="T">The type of symbol to look for</typeparam>
  45. /// <param name="fullName">Fully-qualified name to look up</param>
  46. /// <returns>The symbol with the given name and type,
  47. /// or null if the symbol doesn't exist or has the wrong type</returns>
  48. internal T FindSymbol<T>(string fullName) where T : class, IDescriptor {
  49. IDescriptor result;
  50. descriptorsByName.TryGetValue(fullName, out result);
  51. T descriptor = result as T;
  52. if (descriptor != null) {
  53. return descriptor;
  54. }
  55. foreach (DescriptorPool dependency in dependencies) {
  56. dependency.descriptorsByName.TryGetValue(fullName, out result);
  57. descriptor = result as T;
  58. if (descriptor != null) {
  59. return descriptor;
  60. }
  61. }
  62. return null;
  63. }
  64. /// <summary>
  65. /// Adds a package to the symbol tables. If a package by the same name
  66. /// already exists, that is fine, but if some other kind of symbol
  67. /// exists under the same name, an exception is thrown. If the package
  68. /// has multiple components, this also adds the parent package(s).
  69. /// </summary>
  70. internal void AddPackage(string fullName, FileDescriptor file) {
  71. int dotpos = fullName.LastIndexOf('.');
  72. String name;
  73. if (dotpos != -1) {
  74. AddPackage(fullName.Substring(0, dotpos), file);
  75. name = fullName.Substring(dotpos + 1);
  76. } else {
  77. name = fullName;
  78. }
  79. IDescriptor old;
  80. if (descriptorsByName.TryGetValue(fullName, out old)) {
  81. if (!(old is PackageDescriptor)) {
  82. throw new DescriptorValidationException(file,
  83. "\"" + name + "\" is already defined (as something other than a " +
  84. "package) in file \"" + old.File.Name + "\".");
  85. }
  86. }
  87. // TODO(jonskeet): Check issue 25 wrt the ordering of these parameters
  88. descriptorsByName[fullName] = new PackageDescriptor(fullName, name, file);
  89. }
  90. /// <summary>
  91. /// Adds a symbol to the symbol table.
  92. /// </summary>
  93. /// <exception cref="DescriptorValidationException">The symbol already existed
  94. /// in the symbol table.</exception>
  95. internal void AddSymbol(IDescriptor descriptor) {
  96. ValidateSymbolName(descriptor);
  97. String fullName = descriptor.FullName;
  98. IDescriptor old;
  99. if (descriptorsByName.TryGetValue(fullName, out old)) {
  100. int dotPos = fullName.LastIndexOf('.');
  101. string message;
  102. if (descriptor.File == old.File) {
  103. if (dotPos == -1) {
  104. message = "\"" + fullName + "\" is already defined.";
  105. } else {
  106. message = "\"" + fullName.Substring(dotPos + 1) + "\" is already defined in \"" + fullName.Substring(0, dotPos) + "\".";
  107. }
  108. } else {
  109. message = "\"" + fullName + "\" is already defined in file \"" + old.File.Name + "\".";
  110. }
  111. throw new DescriptorValidationException(descriptor, message);
  112. }
  113. descriptorsByName[fullName] = descriptor;
  114. }
  115. private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$", RegexOptions.Compiled);
  116. /// <summary>
  117. /// Verifies that the descriptor's name is valid (i.e. it contains
  118. /// only letters, digits and underscores, and does not start with a digit).
  119. /// </summary>
  120. /// <param name="descriptor"></param>
  121. private static void ValidateSymbolName(IDescriptor descriptor) {
  122. if (descriptor.Name == "") {
  123. throw new DescriptorValidationException(descriptor, "Missing name.");
  124. }
  125. if (!ValidationRegex.IsMatch(descriptor.Name)) {
  126. throw new DescriptorValidationException(descriptor,
  127. "\"" + descriptor.Name + "\" is not a valid identifier.");
  128. }
  129. }
  130. /// <summary>
  131. /// Returns the field with the given number in the given descriptor,
  132. /// or null if it can't be found.
  133. /// </summary>
  134. internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number) {
  135. FieldDescriptor ret;
  136. fieldsByNumber.TryGetValue(new DescriptorIntPair(messageDescriptor, number), out ret);
  137. return ret;
  138. }
  139. internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number) {
  140. EnumValueDescriptor ret;
  141. enumValuesByNumber.TryGetValue(new DescriptorIntPair(enumDescriptor, number), out ret);
  142. return ret;
  143. }
  144. /// <summary>
  145. /// Adds a field to the fieldsByNumber table.
  146. /// </summary>
  147. /// <exception cref="DescriptorValidationException">A field with the same
  148. /// containing type and number already exists.</exception>
  149. internal void AddFieldByNumber(FieldDescriptor field) {
  150. DescriptorIntPair key = new DescriptorIntPair(field.ContainingType, field.FieldNumber);
  151. FieldDescriptor old;
  152. if (fieldsByNumber.TryGetValue(key, out old)) {
  153. throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
  154. "has already been used in \"" + field.ContainingType.FullName +
  155. "\" by field \"" + old.Name + "\".");
  156. }
  157. fieldsByNumber[key] = field;
  158. }
  159. /// <summary>
  160. /// Adds an enum value to the enumValuesByNumber table. If an enum value
  161. /// with the same type and number already exists, this method does nothing.
  162. /// (This is allowed; the first value defined with the number takes precedence.)
  163. /// </summary>
  164. internal void AddEnumValueByNumber(EnumValueDescriptor enumValue) {
  165. DescriptorIntPair key = new DescriptorIntPair(enumValue.EnumDescriptor, enumValue.Number);
  166. if (!enumValuesByNumber.ContainsKey(key)) {
  167. enumValuesByNumber[key] = enumValue;
  168. }
  169. }
  170. /// <summary>
  171. /// Looks up a descriptor by name, relative to some other descriptor.
  172. /// The name may be fully-qualified (with a leading '.'), partially-qualified,
  173. /// or unqualified. C++-like name lookup semantics are used to search for the
  174. /// matching descriptor.
  175. /// </summary>
  176. public IDescriptor LookupSymbol(string name, IDescriptor relativeTo) {
  177. // TODO(jonskeet): This could be optimized in a number of ways.
  178. IDescriptor result;
  179. if (name.StartsWith(".")) {
  180. // Fully-qualified name.
  181. result = FindSymbol<IDescriptor>(name.Substring(1));
  182. } else {
  183. // If "name" is a compound identifier, we want to search for the
  184. // first component of it, then search within it for the rest.
  185. int firstPartLength = name.IndexOf('.');
  186. string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength);
  187. // We will search each parent scope of "relativeTo" looking for the
  188. // symbol.
  189. StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName);
  190. while (true) {
  191. // Chop off the last component of the scope.
  192. // TODO(jonskeet): Make this more efficient. May not be worth using StringBuilder at all
  193. int dotpos = scopeToTry.ToString().LastIndexOf(".");
  194. if (dotpos == -1) {
  195. result = FindSymbol<IDescriptor>(name);
  196. break;
  197. } else {
  198. scopeToTry.Length = dotpos + 1;
  199. // Append firstPart and try to find.
  200. scopeToTry.Append(firstPart);
  201. result = FindSymbol<IDescriptor>(scopeToTry.ToString());
  202. if (result != null) {
  203. if (firstPartLength != -1) {
  204. // We only found the first part of the symbol. Now look for
  205. // the whole thing. If this fails, we *don't* want to keep
  206. // searching parent scopes.
  207. scopeToTry.Length = dotpos + 1;
  208. scopeToTry.Append(name);
  209. result = FindSymbol<IDescriptor>(scopeToTry.ToString());
  210. }
  211. break;
  212. }
  213. // Not found. Remove the name so we can try again.
  214. scopeToTry.Length = dotpos;
  215. }
  216. }
  217. }
  218. if (result == null) {
  219. throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined.");
  220. } else {
  221. return result;
  222. }
  223. }
  224. /// <summary>
  225. /// Struct used to hold the keys for the fieldByNumber table.
  226. /// </summary>
  227. struct DescriptorIntPair : IEquatable<DescriptorIntPair> {
  228. private readonly int number;
  229. private readonly IDescriptor descriptor;
  230. internal DescriptorIntPair(IDescriptor descriptor, int number) {
  231. this.number = number;
  232. this.descriptor = descriptor;
  233. }
  234. public bool Equals(DescriptorIntPair other) {
  235. return descriptor == other.descriptor
  236. && number == other.number;
  237. }
  238. public override bool Equals(object obj) {
  239. if (obj is DescriptorIntPair) {
  240. return Equals((DescriptorIntPair)obj);
  241. }
  242. return false;
  243. }
  244. public override int GetHashCode() {
  245. return descriptor.GetHashCode() * ((1 << 16) - 1) + number;
  246. }
  247. }
  248. }
  249. }