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