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