2009-08-31 Michael Barker <mike@middlesoft.co.uk>
[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-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
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 void 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                 Console.Error.WriteLine("Unsupported command-line option: " + opt);
214                 Usage();
215             }
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                 HandleOption((string) args[0]);
231                 args.RemoveAt(0);
232             }
233             if ((args.Count < 2)
234                 || (m_apiName == null))
235             {
236                 Usage();
237             }
238             m_inputXmlFilename = (string) args[0];
239             m_outputFilename = (string) args[1];
240         }
241
242         ///////////////////////////////////////////////////////////////////////////
243
244         public string FramingSubnamespace {
245             get {
246                 if (m_framingSubnamespace == null) {
247                     return VersionToken();
248                 } else {
249                     return m_framingSubnamespace;
250                 }
251             }
252         }
253
254         public string ApiNamespaceBase {
255             get {
256                 return "RabbitMQ.Client.Framing."+FramingSubnamespace;
257             }
258         }
259
260         public string ImplNamespaceBase {
261             get {
262                 return "RabbitMQ.Client.Framing.Impl."+FramingSubnamespace;
263             }
264         }
265
266         public void Generate() {
267             LoadSpec();
268             ParseSpec();
269                 ReflectModel();
270             GenerateOutput();
271         }
272
273         public void LoadSpec() {
274             Console.WriteLine("* Loading spec from '" + m_inputXmlFilename + "'");
275             m_spec = new XmlDocument();
276             m_spec.Load(m_inputXmlFilename);
277         }
278
279         public void ParseSpec() {
280             Console.WriteLine("* Parsing spec");
281             if (!m_versionOverridden) {
282                 m_majorVersion = GetInt(m_spec, "/amqp/@major");
283                 m_minorVersion = GetInt(m_spec, "/amqp/@minor");
284             }
285             foreach (XmlNode n in m_spec.SelectNodes("/amqp/constant")) {
286                 m_constants.Add(new DictionaryEntry(GetString(n, "@name"), GetInt(n, "@value")));
287             }
288             foreach (XmlNode n in m_spec.SelectNodes("/amqp/class")) {
289                 m_classes.Add(new AmqpClass(n));
290             }
291             foreach (XmlNode n in m_spec.SelectNodes("/amqp/domain")) {
292                 m_domains[GetString(n, "@name")] = GetString(n, "@type");
293             }
294         }
295
296         public void ReflectModel() {
297             m_modelTypes.Add(m_modelType);
298             for (int i = 0; i < m_modelTypes.Count; i++)
299             {
300                 foreach (Type intf in ((Type) m_modelTypes[i]).GetInterfaces())
301                 {
302                     m_modelTypes.Add(intf);
303                 }
304             }
305         }
306
307         public string ResolveDomain(string d) {
308             while (m_domains[d] != null) {
309                 string newD = (string) m_domains[d];
310                 if (d.Equals(newD))
311                     break;
312                 d = newD;
313             }
314             return d;
315         }
316
317         public string MapDomain(string d) {
318             return (string) m_primitiveTypeMap[ResolveDomain(d)];
319         }
320
321         public string VersionToken() {
322             return "v" + m_majorVersion + "_" + m_minorVersion;
323         }
324
325         public void GenerateOutput() {
326             Console.WriteLine("* Generating code into '" + m_outputFilename + "'");
327             m_outputFile = new StreamWriter(m_outputFilename);
328             EmitPrelude();
329             EmitPublic();
330             EmitPrivate();
331             m_outputFile.Close();
332         }
333
334         public void Emit(object o) {
335             m_outputFile.Write(o);
336         }
337
338         public void EmitLine(object o) {
339             m_outputFile.WriteLine(o);
340         }
341         
342         public void EmitSpecComment(object o) {
343             if (m_emitComments)
344                 EmitLine(o);
345         }
346
347         public void EmitPrelude() {
348             EmitLine("// Autogenerated code. Do not edit.");
349             EmitLine("");
350             EmitLine("using RabbitMQ.Client;");
351             EmitLine("using RabbitMQ.Client.Exceptions;");
352             EmitLine("");
353         }
354
355         public void EmitPublic() {
356             EmitLine("namespace "+ApiNamespaceBase+" {");
357             EmitLine("  public class Protocol: "+ImplNamespaceBase+".ProtocolBase {");
358             EmitLine("    ///<summary>Protocol major version (= "+m_majorVersion+")</summary>");
359             EmitLine("    public override int MajorVersion { get { return " + m_majorVersion + "; } }");
360             EmitLine("    ///<summary>Protocol minor version (= "+m_minorVersion+")</summary>");
361             EmitLine("    public override int MinorVersion { get { return " + m_minorVersion + "; } }");
362             EmitLine("    ///<summary>Protocol API name (= "+m_apiName+")</summary>");
363             EmitLine("    public override string ApiName { get { return \"" + m_apiName + "\"; } }");
364             int port = GetInt(m_spec, "/amqp/@port");
365             EmitLine("    ///<summary>Default TCP port (= "+port+")</summary>");
366             EmitLine("    public override int DefaultPort { get { return " + port + "; } }");
367             EmitLine("");
368             EmitMethodArgumentReader();
369             EmitLine("");
370             EmitContentHeaderReader();
371             EmitLine("  }");
372             EmitLine("  public class Constants {");
373             foreach (DictionaryEntry de in m_constants) {
374                 EmitLine("    ///<summary>(= "+de.Value+")</summary>");
375                 EmitLine("    public const int "+MangleConstant((string) de.Key)+" = "+de.Value+";");
376             }
377             EmitLine("  }");
378             foreach (AmqpClass c in m_classes) {
379                 EmitClassMethods(c);
380             }
381             foreach (AmqpClass c in m_classes) {
382                 if (c.NeedsProperties) {
383                     EmitClassProperties(c);
384                 }
385             }
386             EmitLine("}");
387         }
388
389         public void EmitAutogeneratedSummary(string prefixSpaces, string extra) {
390             EmitLine(prefixSpaces+"/// <summary>Autogenerated type. "+extra+"</summary>");
391         }
392
393         public void EmitClassMethods(AmqpClass c) {
394             foreach (AmqpMethod m in c.m_Methods) {
395                 EmitAutogeneratedSummary("  ",
396                                          "AMQP specification method \""+c.Name+"."+m.Name+"\".");
397                 EmitSpecComment(m.DocumentationCommentVariant("  ", "remarks"));
398                 EmitLine("  public interface I"+MangleMethodClass(c, m)+": IMethod {");
399                 foreach (AmqpField f in m.m_Fields) {
400                     EmitSpecComment(f.DocumentationComment("    "));
401                     EmitLine("    "+MapDomain(f.Domain)+" "+MangleClass(f.Name)+" { get; }");
402                 }
403                 EmitLine("  }");
404             }
405         }
406
407         public bool HasFactoryMethod(AmqpClass c) {
408             foreach (Type t in m_modelTypes) {
409                 foreach (MethodInfo method in t.GetMethods()) {
410                     AmqpContentHeaderFactoryAttribute f = (AmqpContentHeaderFactoryAttribute)
411                         Attribute(method, typeof(AmqpContentHeaderFactoryAttribute));
412                     if (f != null && MangleClass(f.m_contentClass) == MangleClass(c.Name)) {
413                         return true;
414                     }
415                 }
416             }
417             return false;
418         }
419
420         public bool IsBoolean(AmqpField f) {
421             return ResolveDomain(f.Domain) == "bit";
422         }
423
424         public bool IsReferenceType(AmqpField f) {
425             return (bool) m_primitiveTypeFlagMap[ResolveDomain(f.Domain)];
426         }
427
428     public bool IsAmqpClass(Type t)
429     {
430         foreach (AmqpClass c in m_classes)
431         {
432             if (c.Name == t.Name)
433                 return true;
434         }
435         return false;
436     }
437
438         public void EmitClassProperties(AmqpClass c) {
439             bool hasCommonApi = HasFactoryMethod(c);
440             string propertiesBaseClass =
441                 hasCommonApi
442                 ? "RabbitMQ.Client.Impl."+MangleClass(c.Name)+"Properties"
443                 : "RabbitMQ.Client.Impl.ContentHeaderBase";
444             string maybeOverride = hasCommonApi ? "override " : "";
445
446             EmitAutogeneratedSummary("  ",
447                                      "AMQP specification content header properties for "+
448                                      "content class \""+c.Name+"\"");
449             EmitSpecComment(c.DocumentationCommentVariant("  ", "remarks"));
450             EmitLine("  public class "+MangleClass(c.Name)
451                      +"Properties: "+propertiesBaseClass+" {");
452             foreach (AmqpField f in c.m_Fields) {
453                 EmitLine("    private "+MapDomain(f.Domain)+" m_"+MangleMethod(f.Name)+";");
454             }
455             EmitLine("");
456             foreach (AmqpField f in c.m_Fields)
457             {
458                 if (!IsBoolean(f)) {
459                     EmitLine("    private bool m_"+MangleMethod(f.Name)+"_present = false;");
460                 }
461             }
462             EmitLine("");
463             foreach (AmqpField f in c.m_Fields)
464             {
465                 EmitSpecComment(f.DocumentationComment("    ", "@label"));
466                 EmitLine("    public "+maybeOverride+MapDomain(f.Domain)+" "+MangleClass(f.Name)+" {");
467                 EmitLine("      get {");
468                 EmitLine("        return m_"+MangleMethod(f.Name)+";");
469                 EmitLine("      }");
470                 EmitLine("      set {");
471                 if (!IsBoolean(f)) {
472                     EmitLine("        m_"+MangleMethod(f.Name)+"_present = true;");
473                 }
474                 EmitLine("        m_"+MangleMethod(f.Name)+" = value;");
475                 EmitLine("      }");
476                 EmitLine("    }");
477             }
478             EmitLine("");
479             foreach (AmqpField f in c.m_Fields)
480             {
481                 if (!IsBoolean(f)) {
482                     EmitLine("    public "+maybeOverride+"void Clear"+MangleClass(f.Name)+"() { m_"+MangleMethod(f.Name)+"_present = false; }");
483                 }
484             }
485             EmitLine("");
486             EmitLine("    public "+MangleClass(c.Name)+"Properties() {}");
487             EmitLine("    public override int ProtocolClassId { get { return "+c.Index+"; } }");
488             EmitLine("    public override string ProtocolClassName { get { return \""+c.Name+"\"; } }");
489             EmitLine("");
490             EmitLine("    public override void ReadPropertiesFrom(RabbitMQ.Client.Impl.ContentHeaderPropertyReader reader) {");
491             foreach (AmqpField f in c.m_Fields)
492             {
493                 if (IsBoolean(f)) {
494                     EmitLine("      m_"+MangleMethod(f.Name)+" = reader.ReadBit();");
495                 } else {
496                     EmitLine("      m_"+MangleMethod(f.Name)+"_present = reader.ReadPresence();");
497                 }
498             }
499             EmitLine("      reader.FinishPresence();");
500         foreach (AmqpField f in c.m_Fields)
501         {
502                 if (!IsBoolean(f)) {
503                     EmitLine("      if (m_"+MangleMethod(f.Name)+"_present) { m_"+MangleMethod(f.Name)+" = reader.Read"+MangleClass(ResolveDomain(f.Domain))+"(); }");
504                 }
505             }
506             EmitLine("    }");
507             EmitLine("");
508             EmitLine("    public override void WritePropertiesTo(RabbitMQ.Client.Impl.ContentHeaderPropertyWriter writer) {");
509             foreach (AmqpField f in c.m_Fields)
510             {
511                 if (IsBoolean(f)) {
512                     EmitLine("      writer.WriteBit(m_"+MangleMethod(f.Name)+");");
513                 } else {
514                     EmitLine("      writer.WritePresence(m_"+MangleMethod(f.Name)+"_present);");
515                 }
516             }
517             EmitLine("      writer.FinishPresence();");
518         foreach (AmqpField f in c.m_Fields)
519         {
520                 if (!IsBoolean(f)) {
521                     EmitLine("      if (m_"+MangleMethod(f.Name)+"_present) { writer.Write"+MangleClass(ResolveDomain(f.Domain))+"(m_"+MangleMethod(f.Name)+"); }");
522                 }
523             }
524             EmitLine("    }");
525             EmitLine("");
526             EmitLine("    public override void AppendPropertyDebugStringTo(System.Text.StringBuilder sb) {");
527             EmitLine("      sb.Append(\"(\");");
528             {
529                 int remaining = c.m_Fields.Count;
530                 foreach (AmqpField f in c.m_Fields)
531                 {
532                     Emit("      sb.Append(\""+f.Name+"=\");");
533                     if (IsBoolean(f)) {
534                         Emit(" sb.Append(m_"+MangleMethod(f.Name)+");");
535                     } else {
536                         string x = MangleMethod(f.Name);
537                         if (IsReferenceType(f)) {
538                             Emit(" sb.Append(m_"+x+"_present ? (m_"+x+" == null ? \"(null)\" : m_"+x+".ToString()) : \"_\");");
539                         } else {
540                             Emit(" sb.Append(m_"+x+"_present ? m_"+x+".ToString() : \"_\");");
541                         }
542                     }
543                     remaining--;
544                     if (remaining > 0) {
545                         EmitLine(" sb.Append(\", \");");
546                     } else {
547                         EmitLine("");
548                     }
549                 }
550             }
551             EmitLine("      sb.Append(\")\");");
552             EmitLine("    }");
553             EmitLine("  }");
554         }
555
556         public void EmitPrivate() {
557             EmitLine("namespace "+ImplNamespaceBase+" {");
558             EmitLine("  using "+ApiNamespaceBase+";");
559             EmitLine("  public enum ClassId {");
560             foreach (AmqpClass c in m_classes) {
561                 EmitLine("    "+MangleConstant(c.Name)+" = "+c.Index+",");
562             }
563             EmitLine("    Invalid = -1");
564             EmitLine("  }");
565             foreach (AmqpClass c in m_classes) {
566                 EmitClassMethodImplementations(c);
567             }
568             EmitLine("");
569             EmitModelImplementation();
570             EmitLine("}");
571         }
572
573         public void EmitClassMethodImplementations(AmqpClass c) {
574             foreach (AmqpMethod m in c.m_Methods)
575             {
576                 EmitAutogeneratedSummary("  ",
577                                          "Private implementation class - do not use directly.");
578                 EmitLine("  public class "+MangleMethodClass(c,m)
579                          +": RabbitMQ.Client.Impl.MethodBase, I"+MangleMethodClass(c,m)+" {");
580                 EmitLine("    public const int ClassId = "+c.Index+";");
581                 EmitLine("    public const int MethodId = "+m.Index+";");
582                 EmitLine("");
583                 foreach (AmqpField f in m.m_Fields)
584                 {
585                     EmitLine("    public "+MapDomain(f.Domain)+" m_"+MangleMethod(f.Name)+";");
586                 }
587                 EmitLine("");
588                 foreach (AmqpField f in m.m_Fields)
589                 {
590                     EmitLine("    "+MapDomain(f.Domain)+" I"+MangleMethodClass(c,m)+
591                              "."+MangleClass(f.Name)+" { get {"
592                              + " return m_" + MangleMethod(f.Name) + "; } }");
593                 }
594                 EmitLine("");
595                 if (m.m_Fields.Count > 0)
596                 {
597                     EmitLine("    public "+MangleMethodClass(c,m)+"() {}");
598                 }
599                 EmitLine("    public "+MangleMethodClass(c,m)+"(");
600                 {
601                     int remaining = m.m_Fields.Count;
602                     foreach (AmqpField f in m.m_Fields)
603                     {
604                         Emit("      "+MapDomain(f.Domain)+" init"+MangleClass(f.Name));
605                         remaining--;
606                         if (remaining > 0) {
607                             EmitLine(",");
608                         }
609                     }
610                 }
611                 EmitLine(")");
612                 EmitLine("    {");
613                 foreach (AmqpField f in m.m_Fields)
614                 {
615                     EmitLine("      m_" + MangleMethod(f.Name) + " = init" + MangleClass(f.Name) + ";");
616                 }
617                 EmitLine("    }");
618                 EmitLine("");
619                 EmitLine("    public override int ProtocolClassId { get { return "+c.Index+"; } }");
620                 EmitLine("    public override int ProtocolMethodId { get { return "+m.Index+"; } }");
621                 EmitLine("    public override string ProtocolMethodName { get { return \""+c.Name+"."+m.Name+"\"; } }");
622                 EmitLine("    public override bool HasContent { get { return "
623                          +(m.HasContent ? "true" : "false")+"; } }");
624                 EmitLine("");
625                 EmitLine("    public override void ReadArgumentsFrom(RabbitMQ.Client.Impl.MethodArgumentReader reader) {");
626                 foreach (AmqpField f in m.m_Fields)
627                 {
628                     EmitLine("      m_" + MangleMethod(f.Name) + " = reader.Read" + MangleClass(ResolveDomain(f.Domain)) + "();");
629                 }
630                 EmitLine("    }");
631                 EmitLine("");
632                 EmitLine("    public override void WriteArgumentsTo(RabbitMQ.Client.Impl.MethodArgumentWriter writer) {");
633                 foreach (AmqpField f in m.m_Fields)
634                 {
635                     EmitLine("      writer.Write"+MangleClass(ResolveDomain(f.Domain))
636                              + "(m_" + MangleMethod(f.Name) + ");");
637                 }
638                 EmitLine("    }");
639                 EmitLine("");
640                 EmitLine("    public override void AppendArgumentDebugStringTo(System.Text.StringBuilder sb) {");
641                 EmitLine("      sb.Append(\"(\");");
642                 {
643                     int remaining = m.m_Fields.Count;
644                     foreach (AmqpField f in m.m_Fields)
645                     {
646                         Emit("      sb.Append(m_" + MangleMethod(f.Name) + ");");
647                         remaining--;
648                         if (remaining > 0) {
649                             EmitLine(" sb.Append(\",\");");
650                         } else {
651                             EmitLine("");
652                         }
653                     }
654                 }
655                 EmitLine("      sb.Append(\")\");");
656                 EmitLine("    }");
657                 EmitLine("  }");
658             }
659         }
660
661         public void EmitMethodArgumentReader() {
662             EmitLine("    public override RabbitMQ.Client.Impl.MethodBase DecodeMethodFrom(RabbitMQ.Util.NetworkBinaryReader reader) {");
663             EmitLine("      ushort classId = reader.ReadUInt16();");
664             EmitLine("      ushort methodId = reader.ReadUInt16();");
665             EmitLine("");
666             EmitLine("      switch (classId) {");
667             foreach (AmqpClass c in m_classes) {
668                 EmitLine("        case "+c.Index+": {");
669                 EmitLine("          switch (methodId) {");
670                 foreach (AmqpMethod m in c.m_Methods)
671                 {
672                     EmitLine("            case "+m.Index+": {");
673                     EmitLine("              "+ImplNamespaceBase+"."+MangleMethodClass(c,m)+" result = new "+ImplNamespaceBase+"."+MangleMethodClass(c,m)+"();");
674                     EmitLine("              result.ReadArgumentsFrom(new RabbitMQ.Client.Impl.MethodArgumentReader(reader));");
675                     EmitLine("              return result;");
676                     EmitLine("            }");
677                 }
678                 EmitLine("            default: break;");
679                 EmitLine("          }");
680                 EmitLine("          break;");
681                 EmitLine("        }");
682             }
683             EmitLine("        default: break;");
684             EmitLine("      }");
685             EmitLine("      throw new RabbitMQ.Client.Impl.UnknownClassOrMethodException(classId, methodId);");
686             EmitLine("    }");
687         }
688
689         public void EmitContentHeaderReader() {
690             EmitLine("    public override RabbitMQ.Client.Impl.ContentHeaderBase DecodeContentHeaderFrom(RabbitMQ.Util.NetworkBinaryReader reader) {");
691             EmitLine("      ushort classId = reader.ReadUInt16();");
692             EmitLine("");
693             EmitLine("      switch (classId) {");
694             foreach (AmqpClass c in m_classes) {
695                 if (c.NeedsProperties) {
696                     EmitLine("        case "+c.Index+": return new "
697                              +MangleClass(c.Name)+"Properties();");
698                 }
699             }
700             EmitLine("        default: break;");
701             EmitLine("      }");
702             EmitLine("      throw new RabbitMQ.Client.Impl.UnknownClassOrMethodException(classId, 0);");
703             EmitLine("    }");
704         }
705
706         public Attribute Attribute(MemberInfo mi, Type t) {
707             return Attribute(mi.GetCustomAttributes(t, false), t);
708         }
709
710         public Attribute Attribute(ParameterInfo pi, Type t) {
711             return Attribute(pi.GetCustomAttributes(t, false), t);
712         }
713
714         public Attribute Attribute(ICustomAttributeProvider p, Type t) {
715             return Attribute(p.GetCustomAttributes(t, false), t);
716         }
717
718         public Attribute Attribute(IEnumerable attributes, Type t) {
719             if (t.IsSubclassOf(typeof(AmqpApigenAttribute))) {
720                 AmqpApigenAttribute result = null;
721                 foreach (AmqpApigenAttribute candidate in attributes) {
722                     if (candidate.m_namespaceName == null && result == null) {
723                         result = candidate;
724                     }
725                     if (candidate.m_namespaceName == ApiNamespaceBase) {
726                         result = candidate;
727                     }
728                 }
729                 return result;
730             } else {
731                 foreach (Attribute attribute in attributes) {
732                     return attribute;
733                 }
734                 return null;
735             }
736         }
737
738         public void EmitModelImplementation() {
739             EmitLine("  public class Model: RabbitMQ.Client.Impl.ModelBase {");
740             EmitLine("    public Model(RabbitMQ.Client.Impl.ISession session): base(session) {}");
741             ArrayList asynchronousHandlers = new ArrayList();
742             foreach (Type t in m_modelTypes) {
743                 foreach (MethodInfo method in t.GetMethods()) {
744                     if (method.DeclaringType.Namespace != null &&
745                         method.DeclaringType.Namespace.StartsWith("RabbitMQ.Client")) {
746                         if (method.Name.StartsWith("Handle") ||
747                             (Attribute(method, typeof(AmqpAsynchronousHandlerAttribute)) != null))
748                         {
749                             asynchronousHandlers.Add(method);
750                         } else {
751                             MaybeEmitModelMethod(method);
752                         }
753                     }
754                 }
755             }
756             EmitAsynchronousHandlers(asynchronousHandlers);
757             EmitLine("  }");
758         }
759
760         public void EmitContentHeaderFactory(MethodInfo method) {
761             AmqpContentHeaderFactoryAttribute factoryAnnotation = (AmqpContentHeaderFactoryAttribute)
762                 Attribute(method, typeof(AmqpContentHeaderFactoryAttribute));
763             string contentClass = factoryAnnotation.m_contentClass;
764             EmitModelMethodPreamble(method);
765             EmitLine("    {");
766             EmitLine("      return new "+MangleClass(contentClass)+"Properties();");
767             EmitLine("    }");
768         }
769
770         public void MaybeEmitModelMethod(MethodInfo method) {
771             if (method.IsSpecialName) {
772                 // It's some kind of event- or property-related method.
773                 // It shouldn't be autogenerated.
774             } else if (Attribute(method, typeof(AmqpMethodDoNotImplementAttribute)) != null) {
775                 // Skip this method, by request (AmqpMethodDoNotImplement)
776             } else if (Attribute(method, typeof(AmqpContentHeaderFactoryAttribute)) != null) {
777                 EmitContentHeaderFactory(method);
778             } else if (Attribute(method, typeof(AmqpUnsupportedAttribute)) != null) {
779                 EmitModelMethodPreamble(method);
780                 EmitLine("    {");
781                 EmitLine("      throw new UnsupportedMethodException(\""+method.Name+"\");");
782                 EmitLine("    }");
783             } else {
784                 EmitModelMethod(method);
785             }
786         }
787
788         public string SanitisedFullName(Type t) {
789             if (t == typeof(void)) {
790                 return "void";
791             } else {
792                 return t.FullName;
793             }
794         }
795
796         public void EmitModelMethodPreamble(MethodInfo method) {
797             Emit("    public override "+SanitisedFullName(method.ReturnType)+" "+method.Name);
798             ParameterInfo[] parameters = method.GetParameters();
799             int remaining = parameters.Length;
800             if (remaining == 0) {
801                 EmitLine("()");
802             } else {
803                 EmitLine("(");
804                 foreach (ParameterInfo pi in parameters) {
805                     Emit("      "+SanitisedFullName(pi.ParameterType)+" @"+pi.Name);
806                     remaining--;
807                     if (remaining > 0) {
808                         EmitLine(",");
809                     } else {
810                         EmitLine(")");
811                     }
812                 }
813             }
814         }
815
816         public void LookupAmqpMethod(MethodInfo method,
817                                      string methodName,
818                                      out AmqpClass amqpClass,
819                                      out AmqpMethod amqpMethod)
820         {
821             amqpClass = null;
822             amqpMethod = null;
823             
824             // First, try autodetecting the class/method via the
825             // IModel method name.
826
827             foreach (AmqpClass c in m_classes) {
828                 foreach (AmqpMethod m in c.m_Methods)
829                 {
830                     if (methodName.Equals(MangleMethodClass(c,m))) {
831                         amqpClass = c;
832                         amqpMethod = m;
833                         goto stopSearching; // wheee
834                     }
835                 }
836             }
837             stopSearching:
838
839             // If an explicit mapping was provided as an attribute,
840             // then use that instead, whether the autodetect worked or
841             // not.
842
843             {
844                 AmqpMethodMappingAttribute methodMapping =
845                     Attribute(method, typeof(AmqpMethodMappingAttribute)) as AmqpMethodMappingAttribute;
846                 if (methodMapping != null) {
847                     amqpClass = null;
848                     foreach (AmqpClass c in m_classes) {
849                         if (c.Name == methodMapping.m_className) {
850                             amqpClass = c;
851                             break;
852                         }
853                     }
854                     amqpMethod = amqpClass.MethodNamed(methodMapping.m_methodName);
855                 }
856             }
857
858             // At this point, if can't find either the class or the
859             // method, we can't proceed. Complain.
860
861             if (amqpClass == null || amqpMethod == null) {
862                 throw new Exception("Could not find AMQP class or method for IModel method " + method.Name);
863             }
864         }
865
866         public void EmitModelMethod(MethodInfo method) {
867             ParameterInfo[] parameters = method.GetParameters();
868
869             AmqpClass amqpClass = null;
870             AmqpMethod amqpMethod = null;
871             LookupAmqpMethod(method, method.Name, out amqpClass, out amqpMethod);
872
873             string requestImplClass = MangleMethodClass(amqpClass, amqpMethod);
874
875             // At this point, we know which request method to
876             // send. Now compute whether it's an RPC or not.
877
878             AmqpMethod amqpReplyMethod = null;
879             AmqpMethodMappingAttribute replyMapping =
880                 Attribute(method.ReturnTypeCustomAttributes, typeof(AmqpMethodMappingAttribute))
881                 as AmqpMethodMappingAttribute;
882             if (Attribute(method, typeof(AmqpForceOneWayAttribute)) == null &&
883                 (amqpMethod.IsSimpleRpcRequest || replyMapping != null))
884             {
885                 // We're not forcing oneway, and either are a simple
886                 // RPC request, or have an explicit replyMapping
887                 amqpReplyMethod = amqpClass.MethodNamed(replyMapping == null
888                                                         ? (string) amqpMethod.m_ResponseMethods[0]
889                                                         : replyMapping.m_methodName);
890                 if (amqpReplyMethod == null) {
891                     throw new Exception("Could not find AMQP reply method for IModel method " + method.Name);
892                 }
893             }
894
895             // If amqpReplyMethod is null at this point, it's a
896             // one-way operation, and no continuation needs to be
897             // consed up. Otherwise, we should expect a reply of kind
898             // identified by amqpReplyMethod - unless there's a nowait
899             // parameter thrown into the equation!
900             //
901             // Examine the parameters to discover which might be
902             // nowait, content header or content body.
903
904             ParameterInfo nowaitParameter = null;
905             string nowaitExpression = "null";
906             ParameterInfo contentHeaderParameter = null;
907             ParameterInfo contentBodyParameter = null;
908             foreach (ParameterInfo pi in parameters) {
909                 AmqpNowaitArgumentAttribute nwAttr =
910                     Attribute(pi, typeof(AmqpNowaitArgumentAttribute)) as AmqpNowaitArgumentAttribute;
911                 if (nwAttr != null) {
912                     nowaitParameter = pi;
913                     if (nwAttr.m_replacementExpression != null) {
914                         nowaitExpression = nwAttr.m_replacementExpression;
915                     }
916                 }
917                 if (Attribute(pi, typeof(AmqpContentHeaderMappingAttribute)) != null) {
918                     contentHeaderParameter = pi;
919                 }
920                 if (Attribute(pi, typeof(AmqpContentBodyMappingAttribute)) != null) {
921                     contentBodyParameter = pi;
922                 }
923             }
924
925             // Compute expression text for the content header and body.
926
927             string contentHeaderExpr =
928                 contentHeaderParameter == null
929                 ? "null"
930                 : " ("+MangleClass(amqpClass.Name)+"Properties) "+contentHeaderParameter.Name;
931             string contentBodyExpr =
932                 contentBodyParameter == null ? "null" : contentBodyParameter.Name;
933
934             // Emit the method declaration and preamble.
935
936             EmitModelMethodPreamble(method);
937             EmitLine("    {");
938
939             // Emit the code to build the request.
940
941             EmitLine("      "+requestImplClass+" __req = new "+requestImplClass+"();");
942             foreach (ParameterInfo pi in parameters) {
943                 if (pi != contentHeaderParameter &&
944                     pi != contentBodyParameter)
945                 {
946                     if (Attribute(pi, typeof(AmqpUnsupportedAttribute)) != null) {
947                         EmitLine("      if (@"+pi.Name+" != null) {");
948                         EmitLine("        throw new UnsupportedMethodFieldException(\""+method.Name+"\",\""+pi.Name+"\");");
949                         EmitLine("      }");
950                     } else {
951                         AmqpFieldMappingAttribute fieldMapping =
952                             Attribute(pi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
953                         if (fieldMapping != null) {
954                             EmitLine("      __req.m_"+fieldMapping.m_fieldName+" = @" + pi.Name + ";");
955                         } else {
956                             EmitLine("      __req.m_"+pi.Name+" = @" + pi.Name + ";");
957                         }
958                     }
959                 }
960             }
961
962             // If we have a nowait parameter, sometimes that can turn
963             // a ModelRpc call into a ModelSend call.
964
965             if (nowaitParameter != null) {
966                 EmitLine("      if ("+nowaitParameter.Name+") {");
967                 EmitLine("        ModelSend(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
968                 if (method.ReturnType != typeof(void)) {
969                     EmitLine("        return "+nowaitExpression+";");
970                 }
971                 EmitLine("      }");
972             }
973
974             // At this point, perform either a ModelRpc or a
975             // ModelSend.
976
977             if (amqpReplyMethod == null) {
978                 EmitLine("      ModelSend(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
979             } else {
980                 string replyImplClass = MangleMethodClass(amqpClass, amqpReplyMethod);
981
982                 EmitLine("      RabbitMQ.Client.Impl.MethodBase __repBase = ModelRpc(__req,"+contentHeaderExpr+","+contentBodyExpr+");");
983                 EmitLine("      "+replyImplClass+" __rep = __repBase as "+replyImplClass+";");
984                 EmitLine("      if (__rep == null) throw new UnexpectedMethodException(__repBase);");
985
986                 if (method.ReturnType == typeof(void)) {
987                     // No need to further examine the reply.
988                 } else {
989                     // At this point, we have the reply method. Extract values from it.
990                     AmqpFieldMappingAttribute returnMapping =
991                         Attribute(method.ReturnTypeCustomAttributes, typeof(AmqpFieldMappingAttribute))
992                         as AmqpFieldMappingAttribute;
993                     if (returnMapping == null) {
994                         string fieldPrefix = IsAmqpClass(method.ReturnType) ? "m_" : "";
995
996                         // No field mapping --> it's assumed to be a struct to fill in.
997                         EmitLine("      "+method.ReturnType+" __result = new "+method.ReturnType+"();");
998                         foreach (FieldInfo fi in method.ReturnType.GetFields()) {
999                             AmqpFieldMappingAttribute returnFieldMapping =
1000                                 Attribute(fi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
1001                             if (returnFieldMapping != null) {
1002                                 EmitLine("      __result." + fi.Name + " = __rep." + fieldPrefix + returnFieldMapping.m_fieldName + ";");
1003                             } else {
1004                                 EmitLine("      __result." + fi.Name + " = __rep." + fieldPrefix + fi.Name + ";");
1005                             }
1006                         }
1007                         EmitLine("      return __result;");
1008                     } else {
1009                         // Field mapping --> return just the field we're interested in.
1010                         EmitLine("      return __rep.m_"+returnMapping.m_fieldName+";");
1011                     }
1012                 }
1013             }
1014
1015             // All the IO and result-extraction has been done. Emit
1016             // the method postamble.
1017
1018             EmitLine("    }");
1019         }
1020
1021         public void EmitAsynchronousHandlers(ArrayList asynchronousHandlers) {
1022             EmitLine("    public override bool DispatchAsynchronous(RabbitMQ.Client.Impl.Command cmd) {");
1023             EmitLine("      RabbitMQ.Client.Impl.MethodBase __method = (RabbitMQ.Client.Impl.MethodBase) cmd.Method;");
1024             EmitLine("      switch ((__method.ProtocolClassId << 16) | __method.ProtocolMethodId) {");
1025             foreach (MethodInfo method in asynchronousHandlers) {
1026                 string methodName = method.Name;
1027                 if (methodName.StartsWith("Handle")) {
1028                     methodName = methodName.Substring(6);
1029                 }
1030
1031                 AmqpClass amqpClass = null;
1032                 AmqpMethod amqpMethod = null;
1033                 LookupAmqpMethod(method, methodName, out amqpClass, out amqpMethod);
1034
1035                 string implClass = MangleMethodClass(amqpClass, amqpMethod);
1036
1037                 EmitLine("        case "+((amqpClass.Index << 16) | amqpMethod.Index)+": {");
1038                 ParameterInfo[] parameters = method.GetParameters();
1039                 if (parameters.Length > 0) {
1040                     EmitLine("          "+implClass+" __impl = ("+implClass+") __method;");
1041                     EmitLine("          "+method.Name+"(");
1042                     int remaining = parameters.Length;
1043                     foreach (ParameterInfo pi in parameters) {
1044                         if (Attribute(pi, typeof(AmqpContentHeaderMappingAttribute)) != null) {
1045                             Emit("            ("+pi.ParameterType+") cmd.Header");
1046                         } else if (Attribute(pi, typeof(AmqpContentBodyMappingAttribute)) != null) {
1047                             Emit("            cmd.Body");
1048                         } else {
1049                             AmqpFieldMappingAttribute fieldMapping =
1050                                 Attribute(pi, typeof(AmqpFieldMappingAttribute)) as AmqpFieldMappingAttribute;
1051                             Emit("            __impl.m_"+(fieldMapping == null
1052                                                           ? pi.Name
1053                                                           : fieldMapping.m_fieldName));
1054                         }
1055                         remaining--;
1056                         if (remaining > 0) {
1057                             EmitLine(",");
1058                         }
1059                     }
1060                     EmitLine(");");
1061                 } else {
1062                     EmitLine("          "+method.Name+"();");
1063                 }
1064                 EmitLine("          return true;");
1065                 EmitLine("        }");
1066             }
1067             EmitLine("        default: return false;");
1068             EmitLine("      }");
1069             EmitLine("    }");
1070         }
1071     }
1072 }