PageRenderTime 25ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/apigen/Apigen.cs

https://bitbucket.org/rabbitmq/dotnet-client
C# | 1032 lines | 831 code | 94 blank | 107 comment | 145 complexity | 617b2c32fc2376f827f863c76f52ffb4 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, MPL-2.0-no-copyleft-exception
  1. // This source code is dual-licensed under the Apache License, version
  2. // 2.0, and the Mozilla Public License, version 1.1.
  3. //
  4. // The APL v2.0:
  5. //
  6. //---------------------------------------------------------------------------
  7. // Copyright (C) 2007-2009 LShift Ltd., Cohesive Financial
  8. // Technologies LLC., and Rabbit Technologies Ltd.
  9. //
  10. // Licensed under the Apache License, Version 2.0 (the "License");
  11. // you may not use this file except in compliance with the License.
  12. // You may obtain a copy of the License at
  13. //
  14. // http://www.apache.org/licenses/LICENSE-2.0
  15. //
  16. // Unless required by applicable law or agreed to in writing, software
  17. // distributed under the License is distributed on an "AS IS" BASIS,
  18. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. // See the License for the specific language governing permissions and
  20. // limitations under the License.
  21. //---------------------------------------------------------------------------
  22. //
  23. // The MPL v1.1:
  24. //
  25. //---------------------------------------------------------------------------
  26. // The contents of this file are subject to the Mozilla Public License
  27. // Version 1.1 (the "License"); you may not use this file except in
  28. // compliance with the License. You may obtain a copy of the License at
  29. // http://www.rabbitmq.com/mpl.html
  30. //
  31. // Software distributed under the License is distributed on an "AS IS"
  32. // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  33. // License for the specific language governing rights and limitations
  34. // under the License.
  35. //
  36. // The Original Code is The RabbitMQ .NET Client.
  37. //
  38. // The Initial Developers of the Original Code are LShift Ltd,
  39. // Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd.
  40. //
  41. // Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd,
  42. // Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd
  43. // are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial
  44. // Technologies LLC, and Rabbit Technologies Ltd.
  45. //
  46. // Portions created by LShift Ltd are Copyright (C) 2007-2009 LShift
  47. // Ltd. Portions created by Cohesive Financial Technologies LLC are
  48. // Copyright (C) 2007-2009 Cohesive Financial Technologies
  49. // LLC. Portions created by Rabbit Technologies Ltd are Copyright
  50. // (C) 2007-2009 Rabbit Technologies Ltd.
  51. //
  52. // All Rights Reserved.
  53. //
  54. // Contributor(s): ______________________________________.
  55. //
  56. //---------------------------------------------------------------------------
  57. using System;
  58. using System.Collections;
  59. using System.IO;
  60. using System.Reflection;
  61. using System.Text;
  62. using System.Xml;
  63. using RabbitMQ.Client.Apigen.Attributes;
  64. namespace RabbitMQ.Client.Apigen {
  65. public class Apigen {
  66. ///////////////////////////////////////////////////////////////////////////
  67. // Entry point
  68. public static void Main(string[] args) {
  69. Apigen instance = new Apigen(new ArrayList(args));
  70. instance.Generate();
  71. }
  72. ///////////////////////////////////////////////////////////////////////////
  73. // XML utilities
  74. public static XmlNodeList GetNodes(XmlNode n0, string path) {
  75. return n0.SelectNodes(path);
  76. }
  77. public static string GetString(XmlNode n0, string path, string d) {
  78. XmlNode n = n0.SelectSingleNode(path);
  79. return (n == null) ? d : n.InnerText;
  80. }
  81. public static string GetString(XmlNode n0, string path) {
  82. string s = GetString(n0, path, null);
  83. if (s == null) {
  84. throw new Exception("Missing spec XML node: " + path);
  85. }
  86. return s;
  87. }
  88. public static int GetInt(XmlNode n0, string path, int d) {
  89. string s = GetString(n0, path, null);
  90. return (s == null) ? d : int.Parse(s);
  91. }
  92. public static int GetInt(XmlNode n0, string path) {
  93. return int.Parse(GetString(n0, path));
  94. }
  95. ///////////////////////////////////////////////////////////////////////////
  96. // Name manipulation and mangling for C#
  97. public static string MangleConstant(string name) {
  98. // Previously, we used C_STYLE_CONSTANT_NAMES:
  99. /*
  100. return name
  101. .Replace(" ", "_")
  102. .Replace("-", "_").
  103. ToUpper();
  104. */
  105. // ... but TheseKindsOfNames are more in line with .NET style guidelines.
  106. return MangleClass(name);
  107. }
  108. public static ArrayList IdentifierParts(string name) {
  109. ArrayList result = new ArrayList();
  110. foreach (String s1 in name.Split(new Char[] { '-' })) {
  111. foreach (String s2 in s1.Split(new Char[] { ' ' })) {
  112. result.Add(s2);
  113. }
  114. }
  115. return result;
  116. }
  117. public static string MangleClass(string name) {
  118. StringBuilder sb = new StringBuilder();
  119. foreach (String s in IdentifierParts(name)) {
  120. sb.Append(Char.ToUpper(s[0]) + s.Substring(1).ToLower());
  121. }
  122. return sb.ToString();
  123. }
  124. public static string MangleMethod(string name) {
  125. StringBuilder sb = new StringBuilder();
  126. bool useUpper = false;
  127. foreach (String s in IdentifierParts(name)) {
  128. if (useUpper) {
  129. sb.Append(Char.ToUpper(s[0]) + s.Substring(1).ToLower());
  130. } else {
  131. sb.Append(s.ToLower());
  132. useUpper = true;
  133. }
  134. }
  135. return sb.ToString();
  136. }
  137. public static string MangleMethodClass(AmqpClass c, AmqpMethod m) {
  138. return MangleClass(c.Name) + MangleClass(m.Name);
  139. }
  140. ///////////////////////////////////////////////////////////////////////////
  141. public string framingSubnamespace = null;
  142. public string inputXmlFilename;
  143. public string outputFilename;
  144. public XmlDocument spec = null;
  145. public TextWriter outputFile = null;
  146. public bool versionOverridden = false;
  147. public int majorVersion;
  148. public int minorVersion;
  149. public string apiName;
  150. public Type modelType = typeof(RabbitMQ.Client.Impl.IFullModel);
  151. public ArrayList modelTypes = new ArrayList();
  152. public ArrayList constants = new ArrayList();
  153. public ArrayList classes = new ArrayList();
  154. public Hashtable domains = new Hashtable();
  155. public static Hashtable primitiveTypeMap;
  156. public static Hashtable primitiveTypeFlagMap;
  157. static Apigen() {
  158. primitiveTypeMap = new Hashtable();
  159. primitiveTypeFlagMap = new Hashtable();
  160. InitPrimitiveType("octet", "byte", false);
  161. InitPrimitiveType("shortstr", "string", true);
  162. InitPrimitiveType("longstr", "byte[]", true);
  163. InitPrimitiveType("short", "ushort", false);
  164. InitPrimitiveType("long", "uint", false);
  165. InitPrimitiveType("longlong", "ulong", false);
  166. InitPrimitiveType("bit", "bool", false);
  167. InitPrimitiveType("table", "System.Collections.IDictionary", true);
  168. InitPrimitiveType("timestamp", "AmqpTimestamp", false);
  169. InitPrimitiveType("content", "byte[]", true);
  170. }
  171. public static void InitPrimitiveType(string amqpType, string dotnetType, bool isReference)
  172. {
  173. primitiveTypeMap[amqpType] = dotnetType;
  174. primitiveTypeFlagMap[amqpType] = isReference;
  175. }
  176. public void HandleOption(string opt) {
  177. if (opt.StartsWith("/n:")) {
  178. framingSubnamespace = opt.Substring(3);
  179. } else if (opt.StartsWith("/apiName:")) {
  180. apiName = opt.Substring(9);
  181. } else if (opt.StartsWith("/v:")) {
  182. string[] parts = opt.Substring(3).Split(new char[] { '-' });
  183. versionOverridden = true;
  184. majorVersion = int.Parse(parts[0]);
  185. minorVersion = int.Parse(parts[1]);
  186. } else {
  187. Console.Error.WriteLine("Unsupported command-line option: " + opt);
  188. Usage();
  189. }
  190. }
  191. public void Usage() {
  192. Console.Error.WriteLine("Usage: Apigen.exe [options ...] <input-spec-xml> <output-csharp-file>");
  193. Console.Error.WriteLine(" Options include:");
  194. Console.Error.WriteLine(" /apiName:<identifier>");
  195. Console.Error.WriteLine(" /n:<name.space.prefix>");
  196. Console.Error.WriteLine(" /v:<majorversion>-<minorversion>");
  197. Console.Error.WriteLine(" The apiName option is required.");
  198. Environment.Exit(1);
  199. }
  200. public Apigen(ArrayList args) {
  201. while (args.Count > 0 && ((string) args[0]).StartsWith("/")) {
  202. HandleOption((string) args[0]);
  203. args.RemoveAt(0);
  204. }
  205. if ((args.Count < 2)
  206. || (apiName == null))
  207. {
  208. Usage();
  209. }
  210. this.inputXmlFilename = (string) args[0];
  211. this.outputFilename = (string) args[1];
  212. }
  213. ///////////////////////////////////////////////////////////////////////////
  214. public string FramingSubnamespace {
  215. get {
  216. if (framingSubnamespace == null) {
  217. return VersionToken();
  218. } else {
  219. return framingSubnamespace;
  220. }
  221. }
  222. }
  223. public string ApiNamespaceBase {
  224. get {
  225. return "RabbitMQ.Client.Framing."+FramingSubnamespace;
  226. }
  227. }
  228. public string ImplNamespaceBase {
  229. get {
  230. return "RabbitMQ.Client.Framing.Impl."+FramingSubnamespace;
  231. }
  232. }
  233. public void Generate() {
  234. LoadSpec();
  235. ParseSpec();
  236. ReflectModel();
  237. GenerateOutput();
  238. }
  239. public void LoadSpec() {
  240. Console.WriteLine("* Loading spec from '" + this.inputXmlFilename + "'");
  241. this.spec = new XmlDocument();
  242. this.spec.Load(this.inputXmlFilename);
  243. }
  244. public void ParseSpec() {
  245. Console.WriteLine("* Parsing spec");
  246. if (!versionOverridden) {
  247. majorVersion = GetInt(spec, "/amqp/@major");
  248. minorVersion = GetInt(spec, "/amqp/@minor");
  249. }
  250. foreach (XmlNode n in spec.SelectNodes("/amqp/constant")) {
  251. constants.Add(new DictionaryEntry(GetString(n, "@name"), GetInt(n, "@value")));
  252. }
  253. foreach (XmlNode n in spec.SelectNodes("/amqp/class")) {
  254. classes.Add(new AmqpClass(n));
  255. }
  256. foreach (XmlNode n in spec.SelectNodes("/amqp/domain")) {
  257. domains[GetString(n, "@name")] = GetString(n, "@type");
  258. }
  259. }
  260. public void ReflectModel() {
  261. modelTypes.Add(modelType);
  262. for (int i = 0; i < modelTypes.Count; i++) {
  263. foreach (Type intf in ((Type) modelTypes[i]).GetInterfaces()) {
  264. modelTypes.Add(intf);
  265. }
  266. }
  267. }
  268. public string ResolveDomain(string d) {
  269. while (domains[d] != null) {
  270. string newD = (string) domains[d];
  271. if (d.Equals(newD))
  272. break;
  273. d = newD;
  274. }
  275. return d;
  276. }
  277. public string MapDomain(string d) {
  278. return (string) primitiveTypeMap[ResolveDomain(d)];
  279. }
  280. public string VersionToken() {
  281. return "v" + majorVersion + "_" + minorVersion;
  282. }
  283. public void GenerateOutput() {
  284. Console.WriteLine("* Generating code into '" + this.outputFilename + "'");
  285. this.outputFile = new StreamWriter(this.outputFilename);
  286. EmitPrelude();
  287. EmitPublic();
  288. EmitPrivate();
  289. this.outputFile.Close();
  290. }
  291. public void Emit(object o) {
  292. this.outputFile.Write(o);
  293. }
  294. public void EmitLine(object o) {
  295. this.outputFile.WriteLine(o);
  296. }
  297. public void EmitPrelude() {
  298. EmitLine("// Autogenerated code. Do not edit.");
  299. EmitLine("");
  300. EmitLine("using RabbitMQ.Client;");
  301. EmitLine("using RabbitMQ.Client.Exceptions;");
  302. EmitLine("");
  303. }
  304. public void EmitPublic() {
  305. EmitLine("namespace "+ApiNamespaceBase+" {");
  306. EmitLine(" public class Protocol: "+ImplNamespaceBase+".ProtocolBase {");
  307. EmitLine(" ///<summary>Protocol major version (= "+majorVersion+")</summary>");
  308. EmitLine(" public override int MajorVersion { get { return " + majorVersion + "; } }");
  309. EmitLine(" ///<summary>Protocol minor version (= "+minorVersion+")</summary>");
  310. EmitLine(" public override int MinorVersion { get { return " + minorVersion + "; } }");
  311. EmitLine(" ///<summary>Protocol API name (= "+apiName+")</summary>");
  312. EmitLine(" public override string ApiName { get { return \"" + apiName + "\"; } }");
  313. int port = GetInt(spec, "/amqp/@port");
  314. EmitLine(" ///<summary>Default TCP port (= "+port+")</summary>");
  315. EmitLine(" public override int DefaultPort { get { return " + port + "; } }");
  316. EmitLine("");
  317. EmitMethodArgumentReader();
  318. EmitLine("");
  319. EmitContentHeaderReader();
  320. EmitLine(" }");
  321. EmitLine(" public class Constants {");
  322. foreach (DictionaryEntry de in constants) {
  323. EmitLine(" ///<summary>(= "+de.Value+")</summary>");
  324. EmitLine(" public const int "+MangleConstant((string) de.Key)+" = "+de.Value+";");
  325. }
  326. EmitLine(" }");
  327. foreach (AmqpClass c in classes) {
  328. EmitClassMethods(c);
  329. }
  330. foreach (AmqpClass c in classes) {
  331. if (c.NeedsProperties) {
  332. EmitClassProperties(c);
  333. }
  334. }
  335. EmitLine("}");
  336. }
  337. public void EmitAutogeneratedSummary(string prefixSpaces, string extra) {
  338. EmitLine(prefixSpaces+"/// <summary>Autogenerated type. "+extra+"</summary>");
  339. }
  340. public void EmitClassMethods(AmqpClass c) {
  341. foreach (AmqpMethod m in c.Methods) {
  342. EmitAutogeneratedSummary(" ",
  343. "AMQP specification method \""+c.Name+"."+m.Name+"\".");
  344. EmitLine(m.DocumentationCommentVariant(" ", "remarks"));
  345. EmitLine(" public interface I"+MangleMethodClass(c, m)+": IMethod {");
  346. foreach (AmqpField f in m.Fields) {
  347. EmitLine(f.DocumentationComment(" "));
  348. EmitLine(" "+MapDomain(f.Domain)+" "+MangleClass(f.Name)+" { get; }");
  349. }
  350. EmitLine(" }");
  351. }
  352. }
  353. public bool HasFactoryMethod(AmqpClass c) {
  354. foreach (Type t in modelTypes) {
  355. foreach (MethodInfo method in t.GetMethods()) {
  356. AmqpContentHeaderFactoryAttribute f = (AmqpContentHeaderFactoryAttribute)
  357. Attribute(method, typeof(AmqpContentHeaderFactoryAttribute));
  358. if (f != null && MangleClass(f.m_contentClass) == MangleClass(c.Name)) {
  359. return true;
  360. }
  361. }
  362. }
  363. return false;
  364. }
  365. public bool IsBoolean(AmqpField f) {
  366. return ResolveDomain(f.Domain) == "bit";
  367. }
  368. public bool IsReferenceType(AmqpField f) {
  369. return (bool) primitiveTypeFlagMap[ResolveDomain(f.Domain)];
  370. }
  371. public void EmitClassProperties(AmqpClass c) {
  372. bool hasCommonApi = HasFactoryMethod(c);
  373. string propertiesBaseClass =
  374. hasCommonApi
  375. ? "RabbitMQ.Client.Impl."+MangleClass(c.Name)+"Properties"
  376. : "RabbitMQ.Client.Impl.ContentHeaderBase";
  377. string maybeOverride = hasCommonApi ? "override " : "";
  378. EmitAutogeneratedSummary(" ",
  379. "AMQP specification content header properties for "+
  380. "content class \""+c.Name+"\"");
  381. EmitLine(c.DocumentationCommentVariant(" ", "remarks"));
  382. EmitLine(" public class "+MangleClass(c.Name)
  383. +"Properties: "+propertiesBaseClass+" {");
  384. foreach (AmqpField f in c.Fields) {
  385. EmitLine(" private "+MapDomain(f.Domain)+" m_"+MangleMethod(f.Name)+";");
  386. }
  387. EmitLine("");
  388. foreach (AmqpField f in c.Fields) {
  389. if (!IsBoolean(f)) {
  390. EmitLine(" private bool "+MangleMethod(f.Name)+"_present = false;");
  391. }
  392. }
  393. EmitLine("");
  394. foreach (AmqpField f in c.Fields) {
  395. EmitLine(f.DocumentationComment(" ", "@label"));
  396. EmitLine(" public "+maybeOverride+MapDomain(f.Domain)+" "+MangleClass(f.Name)+" {");
  397. EmitLine(" get {");
  398. EmitLine(" return m_"+MangleMethod(f.Name)+";");
  399. EmitLine(" }");
  400. EmitLine(" set {");
  401. if (!IsBoolean(f)) {
  402. EmitLine(" "+MangleMethod(f.Name)+"_present = true;");
  403. }
  404. EmitLine(" m_"+MangleMethod(f.Name)+" = value;");
  405. EmitLine(" }");
  406. EmitLine(" }");
  407. }
  408. EmitLine("");
  409. foreach (AmqpField f in c.Fields) {
  410. if (!IsBoolean(f)) {
  411. EmitLine(" public "+maybeOverride+"void Clear"+MangleClass(f.Name)+"() { "+MangleMethod(f.Name)+"_present = false; }");
  412. }
  413. }
  414. EmitLine("");
  415. EmitLine(" public "+MangleClass(c.Name)+"Properties() {}");
  416. EmitLine(" public override int ProtocolClassId { get { return "+c.Index+"; } }");
  417. EmitLine(" public override string ProtocolClassName { get { return \""+c.Name+"\"; } }");
  418. EmitLine("");
  419. EmitLine(" public override void ReadPropertiesFrom(RabbitMQ.Client.Impl.ContentHeaderPropertyReader reader) {");
  420. foreach (AmqpField f in c.Fields) {
  421. if (IsBoolean(f)) {
  422. EmitLine(" m_"+MangleMethod(f.Name)+" = reader.ReadBit();");
  423. } else {
  424. EmitLine(" "+MangleMethod(f.Name)+"_present = reader.ReadPresence();");
  425. }
  426. }
  427. EmitLine(" reader.FinishPresence();");
  428. foreach (AmqpField f in c.Fields) {
  429. if (!IsBoolean(f)) {
  430. EmitLine(" if ("+MangleMethod(f.Name)+"_present) { m_"+MangleMethod(f.Name)+" = reader.Read"+MangleClass(ResolveDomain(f.Domain))+"(); }");
  431. }
  432. }
  433. EmitLine(" }");
  434. EmitLine("");
  435. EmitLine(" public override void WritePropertiesTo(RabbitMQ.Client.Impl.ContentHeaderPropertyWriter writer) {");
  436. foreach (AmqpField f in c.Fields) {
  437. if (IsBoolean(f)) {
  438. EmitLine(" writer.WriteBit(m_"+MangleMethod(f.Name)+");");
  439. } else {
  440. EmitLine(" writer.WritePresence("+MangleMethod(f.Name)+"_present);");
  441. }
  442. }
  443. EmitLine(" writer.FinishPresence();");
  444. foreach (AmqpField f in c.Fields) {
  445. if (!IsBoolean(f)) {
  446. EmitLine(" if ("+MangleMethod(f.Name)+"_present) { writer.Write"+MangleClass(ResolveDomain(f.Domain))+"(m_"+MangleMethod(f.Name)+"); }");
  447. }
  448. }
  449. EmitLine(" }");
  450. EmitLine("");
  451. EmitLine(" public override void AppendPropertyDebugStringTo(System.Text.StringBuilder sb) {");
  452. EmitLine(" sb.Append(\"(\");");
  453. {
  454. int remaining = c.Fields.Count;
  455. foreach (AmqpField f in c.Fields) {
  456. Emit(" sb.Append(\""+f.Name+"=\");");
  457. if (IsBoolean(f)) {
  458. Emit(" sb.Append(m_"+MangleMethod(f.Name)+");");
  459. } else {
  460. string x = MangleMethod(f.Name);
  461. if (IsReferenceType(f)) {
  462. Emit(" sb.Append("+x+"_present ? (m_"+x+" == null ? \"(null)\" : m_"+x+".ToString()) : \"_\");");
  463. } else {
  464. Emit(" sb.Append("+x+"_present ? m_"+x+".ToString() : \"_\");");
  465. }
  466. }
  467. remaining--;
  468. if (remaining > 0) {
  469. EmitLine(" sb.Append(\", \");");
  470. } else {
  471. EmitLine("");
  472. }
  473. }
  474. }
  475. EmitLine(" sb.Append(\")\");");
  476. EmitLine(" }");
  477. EmitLine(" }");
  478. }
  479. public void EmitPrivate() {
  480. EmitLine("namespace "+ImplNamespaceBase+" {");
  481. EmitLine(" using "+ApiNamespaceBase+";");
  482. EmitLine(" public enum ClassId {");
  483. foreach (AmqpClass c in classes) {
  484. EmitLine(" "+MangleConstant(c.Name)+" = "+c.Index+",");
  485. }
  486. EmitLine(" Invalid = -1");
  487. EmitLine(" }");
  488. foreach (AmqpClass c in classes) {
  489. EmitClassMethodImplementations(c);
  490. }
  491. EmitLine("");
  492. EmitModelImplementation();
  493. EmitLine("}");
  494. }
  495. public void EmitClassMethodImplementations(AmqpClass c) {
  496. foreach (AmqpMethod m in c.Methods) {
  497. EmitAutogeneratedSummary(" ",
  498. "Private implementation class - do not use directly.");
  499. EmitLine(" public class "+MangleMethodClass(c,m)
  500. +": RabbitMQ.Client.Impl.MethodBase, I"+MangleMethodClass(c,m)+" {");
  501. EmitLine(" public const int ClassId = "+c.Index+";");
  502. EmitLine(" public const int MethodId = "+m.Index+";");
  503. EmitLine("");
  504. foreach (AmqpField f in m.Fields) {
  505. EmitLine(" public "+MapDomain(f.Domain)+" m_"+MangleMethod(f.Name)+";");
  506. }
  507. EmitLine("");
  508. foreach (AmqpField f in m.Fields) {
  509. EmitLine(" "+MapDomain(f.Domain)+" I"+MangleMethodClass(c,m)+
  510. "."+MangleClass(f.Name)+" { get {"
  511. +" return m_"+MangleMethod(f.Name)+"; } }");
  512. }
  513. EmitLine("");
  514. if (m.Fields.Count > 0) {
  515. EmitLine(" public "+MangleMethodClass(c,m)+"() {}");
  516. }
  517. EmitLine(" public "+MangleMethodClass(c,m)+"(");
  518. {
  519. int remaining = m.Fields.Count;
  520. foreach (AmqpField f in m.Fields) {
  521. Emit(" "+MapDomain(f.Domain)+" init"+MangleClass(f.Name));
  522. remaining--;
  523. if (remaining > 0) {
  524. EmitLine(",");
  525. }
  526. }
  527. }
  528. EmitLine(")");
  529. EmitLine(" {");
  530. foreach (AmqpField f in m.Fields) {
  531. EmitLine(" m_"+MangleMethod(f.Name)+" = init"+MangleClass(f.Name)+";");
  532. }
  533. EmitLine(" }");
  534. EmitLine("");
  535. EmitLine(" public override int ProtocolClassId { get { return "+c.Index+"; } }");
  536. EmitLine(" public override int ProtocolMethodId { get { return "+m.Index+"; } }");
  537. EmitLine(" public override string ProtocolMethodName { get { return \""+c.Name+"."+m.Name+"\"; } }");
  538. EmitLine(" public override bool HasContent { get { return "
  539. +(m.HasContent ? "true" : "false")+"; } }");
  540. EmitLine("");
  541. EmitLine(" public override void ReadArgumentsFrom(RabbitMQ.Client.Impl.MethodArgumentReader reader) {");
  542. foreach (AmqpField f in m.Fields) {
  543. EmitLine(" m_"+MangleMethod(f.Name)+" = reader.Read"+MangleClass(ResolveDomain(f.Domain))+"();");
  544. }
  545. EmitLine(" }");
  546. EmitLine("");
  547. EmitLine(" public override void WriteArgumentsTo(RabbitMQ.Client.Impl.MethodArgumentWriter writer) {");
  548. foreach (AmqpField f in m.Fields) {
  549. EmitLine(" writer.Write"+MangleClass(ResolveDomain(f.Domain))
  550. +"(m_"+MangleMethod(f.Name)+");");
  551. }
  552. EmitLine(" }");
  553. EmitLine("");
  554. EmitLine(" public override void AppendArgumentDebugStringTo(System.Text.StringBuilder sb) {");
  555. EmitLine(" sb.Append(\"(\");");
  556. {
  557. int remaining = m.Fields.Count;
  558. foreach (AmqpField f in m.Fields) {
  559. Emit(" sb.Append(m_"+MangleMethod(f.Name)+");");
  560. remaining--;
  561. if (remaining > 0) {
  562. EmitLine(" sb.Append(\",\");");
  563. } else {
  564. EmitLine("");
  565. }
  566. }
  567. }
  568. EmitLine(" sb.Append(\")\");");
  569. EmitLine(" }");
  570. EmitLine(" }");
  571. }
  572. }
  573. public void EmitMethodArgumentReader() {
  574. EmitLine(" public override RabbitMQ.Client.Impl.MethodBase DecodeMethodFrom(RabbitMQ.Util.NetworkBinaryReader reader) {");
  575. EmitLine(" ushort classId = reader.ReadUInt16();");
  576. EmitLine(" ushort methodId = reader.ReadUInt16();");
  577. EmitLine("");
  578. EmitLine(" switch (classId) {");
  579. foreach (AmqpClass c in classes) {
  580. EmitLine(" case "+c.Index+": {");
  581. EmitLine(" switch (methodId) {");
  582. foreach (AmqpMethod m in c.Methods) {
  583. EmitLine(" case "+m.Index+": {");
  584. EmitLine(" "+ImplNamespaceBase+"."+MangleMethodClass(c,m)+" result = new "+ImplNamespaceBase+"."+MangleMethodClass(c,m)+"();");
  585. EmitLine(" result.ReadArgumentsFrom(new RabbitMQ.Client.Impl.MethodArgumentReader(reader));");
  586. EmitLine(" return result;");
  587. EmitLine(" }");
  588. }
  589. EmitLine(" default: break;");
  590. EmitLine(" }");
  591. EmitLine(" break;");
  592. EmitLine(" }");
  593. }
  594. EmitLine(" default: break;");
  595. EmitLine(" }");
  596. EmitLine(" throw new RabbitMQ.Client.Impl.UnknownClassOrMethodException(classId, methodId);");
  597. EmitLine(" }");
  598. }
  599. public void EmitContentHeaderReader() {
  600. EmitLine(" public override RabbitMQ.Client.Impl.ContentHeaderBase DecodeContentHeaderFrom(RabbitMQ.Util.NetworkBinaryReader reader) {");
  601. EmitLine(" ushort classId = reader.ReadUInt16();");
  602. EmitLine("");
  603. EmitLine(" switch (classId) {");
  604. foreach (AmqpClass c in classes) {
  605. if (c.NeedsProperties) {
  606. EmitLine(" case "+c.Index+": return new "
  607. +MangleClass(c.Name)+"Properties();");
  608. }
  609. }
  610. EmitLine(" default: break;");
  611. EmitLine(" }");
  612. EmitLine(" throw new RabbitMQ.Client.Impl.UnknownClassOrMethodException(classId, 0);");
  613. EmitLine(" }");
  614. }
  615. public Attribute Attribute(MemberInfo mi, Type t) {
  616. return Attribute(mi.GetCustomAttributes(t, false), t);
  617. }
  618. public Attribute Attribute(ParameterInfo pi, Type t) {
  619. return Attribute(pi.GetCustomAttributes(t, false), t);
  620. }
  621. public Attribute Attribute(ICustomAttributeProvider p, Type t) {
  622. return Attribute(p.GetCustomAttributes(t, false), t);
  623. }
  624. public Attribute Attribute(IEnumerable attributes, Type t) {
  625. if (t.IsSubclassOf(typeof(AmqpApigenAttribute))) {
  626. AmqpApigenAttribute result = null;
  627. foreach (AmqpApigenAttribute candidate in attributes) {
  628. if (candidate.m_namespaceName == null && result == null) {
  629. result = candidate;
  630. }
  631. if (candidate.m_namespaceName == ApiNamespaceBase) {
  632. result = candidate;
  633. }
  634. }
  635. return result;
  636. } else {
  637. foreach (Attribute attribute in attributes) {
  638. return attribute;
  639. }
  640. return null;
  641. }
  642. }
  643. public void EmitModelImplementation() {
  644. EmitLine(" public class Model: RabbitMQ.Client.Impl.ModelBase {");
  645. EmitLine(" public Model(RabbitMQ.Client.Impl.ISession session): base(session) {}");
  646. ArrayList asynchronousHandlers = new ArrayList();
  647. foreach (Type t in modelTypes) {
  648. foreach (MethodInfo method in t.GetMethods()) {
  649. if (method.DeclaringType.Namespace != null &&
  650. method.DeclaringType.Namespace.StartsWith("RabbitMQ.Client")) {
  651. if (method.Name.StartsWith("Handle") ||
  652. (Attribute(method, typeof(AmqpAsynchronousHandlerAttribute)) != null))
  653. {
  654. asynchronousHandlers.Add(method);
  655. } else {
  656. MaybeEmitModelMethod(method);
  657. }
  658. }
  659. }
  660. }
  661. EmitAsynchronousHandlers(asynchronousHandlers);
  662. EmitLine(" }");
  663. }
  664. public void EmitContentHeaderFactory(MethodInfo method) {
  665. AmqpContentHeaderFactoryAttribute factoryAnnotation = (AmqpContentHeaderFactoryAttribute)
  666. Attribute(method, typeof(AmqpContentHeaderFactoryAttribute));
  667. string contentClass = factoryAnnotation.m_contentClass;
  668. EmitModelMethodPreamble(method);
  669. EmitLine(" {");
  670. EmitLine(" return new "+MangleClass(contentClass)+"Properties();");
  671. EmitLine(" }");
  672. }
  673. public void MaybeEmitModelMethod(MethodInfo method) {
  674. if (method.IsSpecialName) {
  675. // It's some kind of event- or property-related method.
  676. // It shouldn't be autogenerated.
  677. } else if (Attribute(method, typeof(AmqpMethodDoNotImplementAttribute)) != null) {
  678. // Skip this method, by request (AmqpMethodDoNotImplement)
  679. } else if (Attribute(method, typeof(AmqpContentHeaderFactoryAttribute)) != null) {
  680. EmitContentHeaderFactory(method);
  681. } else if (Attribute(method, typeof(AmqpUnsupportedAttribute)) != null) {
  682. EmitModelMethodPreamble(method);
  683. EmitLine(" {");
  684. EmitLine(" throw new UnsupportedMethodException(\""+method.Name+"\");");
  685. EmitLine(" }");
  686. } else {
  687. EmitModelMethod(method);
  688. }
  689. }
  690. public string SanitisedFullName(Type t) {
  691. if (t == typeof(void)) {
  692. return "void";
  693. } else {
  694. return t.FullName;
  695. }
  696. }
  697. public void EmitModelMethodPreamble(MethodInfo method) {
  698. Emit(" public override "+SanitisedFullName(method.ReturnType)+" "+method.Name);
  699. ParameterInfo[] parameters = method.GetParameters();
  700. int remaining = parameters.Length;
  701. if (remaining == 0) {
  702. EmitLine("()");
  703. } else {
  704. EmitLine("(");
  705. foreach (ParameterInfo pi in parameters) {
  706. Emit(" "+SanitisedFullName(pi.ParameterType)+" @"+pi.Name);
  707. remaining--;
  708. if (remaining > 0) {
  709. EmitLine(",");
  710. } else {
  711. EmitLine(")");
  712. }
  713. }
  714. }
  715. }
  716. public void LookupAmqpMethod(MethodInfo method,
  717. string methodName,
  718. out AmqpClass amqpClass,
  719. out AmqpMethod amqpMethod)
  720. {
  721. amqpClass = null;
  722. amqpMethod = null;
  723. // First, try autodetecting the class/method via the
  724. // IModel method name.
  725. foreach (AmqpClass c in classes) {
  726. foreach (AmqpMethod m in c.Methods) {
  727. if (methodName.Equals(MangleMethodClass(c,m))) {
  728. amqpClass = c;
  729. amqpMethod = m;
  730. goto stopSearching; // wheee
  731. }
  732. }
  733. }
  734. stopSearching:
  735. // If an explicit mapping was provided as an attribute,
  736. // then use that instead, whether the autodetect worked or
  737. // not.
  738. {
  739. AmqpMethodMappingAttribute methodMapping =
  740. Attribute(method, typeof(AmqpMethodMappingAttribute)) as AmqpMethodMappingAttribute;
  741. if (methodMapping != null) {
  742. amqpClass = null;
  743. foreach (AmqpClass c in classes) {
  744. if (c.Name == methodMapping.m_className) {
  745. amqpClass = c;
  746. break;
  747. }
  748. }
  749. amqpMethod = amqpClass.MethodNamed(methodMapping.m_methodName);
  750. }
  751. }
  752. // At this point, if can't find either the class or the
  753. // method, we can't proceed. Complain.
  754. if (amqpClass == null || amqpMethod == null) {
  755. throw new Exception("Could not find AMQP class or method for IModel method " + method.Name);
  756. }
  757. }
  758. public void EmitModelMethod(MethodInfo method) {
  759. ParameterInfo[] parameters = method.GetParameters();
  760. AmqpClass amqpClass = null;
  761. AmqpMethod amqpMethod = null;
  762. LookupAmqpMethod(method, method.Name, out amqpClass, out amqpMethod);
  763. string requestImplClass = MangleMethodClass(amqpClass, amqpMethod);
  764. // At this point, we know which request method to
  765. // send. Now compute whether it's an RPC or not.
  766. AmqpMethod amqpReplyMethod = null;
  767. AmqpMethodMappingAttribute replyMapping =
  768. Attribute(method.ReturnTypeCustomAttributes, typeof(AmqpMethodMappingAttribute))
  769. as AmqpMethodMappingAttribute;
  770. if (Attribute(method, typeof(AmqpForceOneWayAttribute)) == null &&
  771. (amqpMethod.IsSimpleRpcRequest || replyMapping != null))
  772. {
  773. // We're not forcing oneway, and either are a simple
  774. // RPC request, or have an explicit replyMapping
  775. amqpReplyMethod = amqpClass.MethodNamed(replyMapping == null
  776. ? (string) amqpMethod.ResponseMethods[0]
  777. : replyMapping.m_methodName);
  778. if (amqpReplyMethod == null) {
  779. throw new Exception("Could not find AMQP reply method for IModel method " + method.Name);
  780. }
  781. }
  782. // If amqpReplyMethod is null at this point, it's a
  783. // one-way operation, and no continuation needs to be
  784. // consed up. Otherwise, we should expect a reply of kind
  785. // identified by amqpReplyMethod - unless there's a nowait
  786. // parameter thrown into the equation!
  787. //
  788. // Examine the parameters to discover which might be
  789. // nowait, content header or content body.
  790. ParameterInfo nowaitParameter = null;
  791. string nowaitExpression = "null";
  792. ParameterInfo contentHeaderParameter = null;
  793. ParameterInfo contentBodyParameter = null;
  794. foreach (ParameterInfo pi in parameters) {
  795. AmqpNowaitArgumentAttribute nwAttr =
  796. Attribute(pi, typeof(AmqpNowaitArgumentAttribute)) as AmqpNowaitArgumentAttribute;
  797. if (nwAttr != null) {
  798. nowaitParameter = pi;
  799. if (nwAttr.m_replacementExpression != null) {
  800. nowaitExpression = nwAttr.m_replacementExpression;
  801. }
  802. }
  803. if (Attribute(pi, typeof(AmqpContentHeaderMappingAttribute)) != null) {
  804. contentHeaderParameter = pi;
  805. }
  806. if (Attribute(pi, typeof(AmqpContentBodyMappingAttribute)) != null) {
  807. contentBodyParameter = pi;
  808. }
  809. }
  810. // Compute expression text for the content header and body.
  811. string contentHeaderExpr =
  812. contentHeaderParameter == null
  813. ? "null"
  814. : " ("+MangleClass(amqpClass.Name)+"Properties) "+contentHeaderParameter.Name;
  815. string contentBodyExpr =
  816. contentBodyParameter == null ? "null" : contentBodyParameter.Name;
  817. // Emit the method declaration and preamble.
  818. EmitModelMethodPreamble(method);
  819. EmitLine(" {");
  820. // Emit the code to build the request.
  821. EmitLine(" "+requestImplClass+" __req = new "+requestImplClass+"();");
  822. foreach (ParameterInfo pi in parameters) {
  823. if (pi != contentHeaderParameter &&
  824. pi != contentBodyParameter)
  825. {
  826. if (Attribute(pi, typeof(AmqpUnsupportedAttribute)) != null) {
  827. EmitLine(" if (@"+pi.Name+" != null) {");
  828. EmitLine(" throw new UnsupportedMethodFieldException(\""+method.Name+"\",\""+pi.Name+"\");");
  829. EmitLine(" }");
  830. } else {
  831. AmqpFieldMappingAttribute fieldMapping =
  832. Attribute(pi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
  833. if (fieldMapping != null) {
  834. EmitLine(" __req.m_"+fieldMapping.m_fieldName+" = @" + pi.Name + ";");
  835. } else {
  836. EmitLine(" __req.m_"+pi.Name+" = @" + pi.Name + ";");
  837. }
  838. }
  839. }
  840. }
  841. // If we have a nowait parameter, sometimes that can turn
  842. // a ModelRpc call into a ModelSend call.
  843. if (nowaitParameter != null) {
  844. EmitLine(" if ("+nowaitParameter.Name+") {");
  845. EmitLine(" ModelSend(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
  846. if (method.ReturnType != typeof(void)) {
  847. EmitLine(" return "+nowaitExpression+";");
  848. }
  849. EmitLine(" }");
  850. }
  851. // At this point, perform either a ModelRpc or a
  852. // ModelSend.
  853. if (amqpReplyMethod == null) {
  854. EmitLine(" ModelSend(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
  855. } else {
  856. string replyImplClass = MangleMethodClass(amqpClass, amqpReplyMethod);
  857. EmitLine(" RabbitMQ.Client.Impl.MethodBase __repBase = ModelRpc(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
  858. EmitLine(" "+replyImplClass+" __rep = __repBase as "+replyImplClass+";");
  859. EmitLine(" if (__rep == null) throw new UnexpectedMethodException(__repBase);");
  860. if (method.ReturnType == typeof(void)) {
  861. // No need to further examine the reply.
  862. } else {
  863. // At this point, we have the reply method. Extract values from it.
  864. AmqpFieldMappingAttribute returnMapping =
  865. Attribute(method.ReturnTypeCustomAttributes, typeof(AmqpFieldMappingAttribute))
  866. as AmqpFieldMappingAttribute;
  867. if (returnMapping == null) {
  868. // No field mapping --> it's assumed to be a struct to fill in.
  869. EmitLine(" "+method.ReturnType+" __result = new "+method.ReturnType+"();");
  870. foreach (FieldInfo fi in method.ReturnType.GetFields()) {
  871. AmqpFieldMappingAttribute returnFieldMapping =
  872. Attribute(fi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
  873. if (returnFieldMapping != null) {
  874. EmitLine(" __result."+fi.Name+" = __rep.m_"+returnFieldMapping.m_fieldName+";");
  875. } else {
  876. EmitLine(" __result."+fi.Name+" = __rep.m_"+fi.Name+";");
  877. }
  878. }
  879. EmitLine(" return __result;");
  880. } else {
  881. // Field mapping --> return just the field we're interested in.
  882. EmitLine(" return __rep.m_"+returnMapping.m_fieldName+";");
  883. }
  884. }
  885. }
  886. // All the IO and result-extraction has been done. Emit
  887. // the method postamble.
  888. EmitLine(" }");
  889. }
  890. public void EmitAsynchronousHandlers(ArrayList asynchronousHandlers) {
  891. EmitLine(" public override bool DispatchAsynchronous(RabbitMQ.Client.Impl.Command cmd) {");
  892. EmitLine(" RabbitMQ.Client.Impl.MethodBase __method = (RabbitMQ.Client.Impl.MethodBase) cmd.Method;");
  893. EmitLine(" switch ((__method.ProtocolClassId << 16) | __method.ProtocolMethodId) {");
  894. foreach (MethodInfo method in asynchronousHandlers) {
  895. string methodName = method.Name;
  896. if (methodName.StartsWith("Handle")) {
  897. methodName = methodName.Substring(6);
  898. }
  899. AmqpClass amqpClass = null;
  900. AmqpMethod amqpMethod = null;
  901. LookupAmqpMethod(method, methodName, out amqpClass, out amqpMethod);
  902. string implClass = MangleMethodClass(amqpClass, amqpMethod);
  903. EmitLine(" case "+((amqpClass.Index << 16) | amqpMethod.Index)+": {");
  904. ParameterInfo[] parameters = method.GetParameters();
  905. if (parameters.Length > 0) {
  906. EmitLine(" "+implClass+" __impl = ("+implClass+") __method;");
  907. EmitLine(" "+method.Name+"(");
  908. int remaining = parameters.Length;
  909. foreach (ParameterInfo pi in parameters) {
  910. if (Attribute(pi, typeof(AmqpContentHeaderMappingAttribute)) != null) {
  911. Emit(" ("+pi.ParameterType+") cmd.Header");
  912. } else if (Attribute(pi, typeof(AmqpContentBodyMappingAttribute)) != null) {
  913. Emit(" cmd.Body");
  914. } else {
  915. AmqpFieldMappingAttribute fieldMapping =
  916. Attribute(pi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
  917. Emit(" __impl.m_"+(fieldMapping == null
  918. ? pi.Name
  919. : fieldMapping.m_fieldName));
  920. }
  921. remaining--;
  922. if (remaining > 0) {
  923. EmitLine(",");
  924. }
  925. }
  926. EmitLine(");");
  927. } else {
  928. EmitLine(" "+method.Name+"();");
  929. }
  930. EmitLine(" return true;");
  931. EmitLine(" }");
  932. }
  933. EmitLine(" default: return false;");
  934. EmitLine(" }");
  935. EmitLine(" }");
  936. }
  937. }
  938. }