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