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