Merge pull request #1032 from miguelzf/master
[mono.git] / mcs / class / System.Runtime.Remoting / System.Runtime.Remoting.MetadataServices / MetaDataCodeGenerator.cs
1 //
2 // System.Runtime.Remoting.MetadataServices.MetaDataCodeGenerator
3 //
4 // Authors:
5 //              Lluis Sanchez Gual (lluis@ximian.com)
6 //
7 // (C) 2003 Novell, Inc
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Collections;
32 using System.IO;
33 using System.Xml;
34 using System.Reflection;
35 using System.Runtime.Remoting;
36 using System.Runtime.Remoting.Metadata;
37
38 namespace System.Runtime.Remoting.MetadataServices
39 {
40         internal class MetaDataCodeGenerator
41         {
42                 XmlDocument doc;
43                 CodeFile currentFile;
44                 XmlNamespaceManager nsManager;
45                 Hashtable sudsTypes;
46                 
47                 public void GenerateCode (bool clientProxy, string outputDirectory, Stream inputStream, 
48                         ArrayList outCodeStreamList, string proxyUrl, string proxyNamespace)
49                 {
50                         doc = new XmlDocument ();
51                         doc.Load (inputStream);
52                         
53                         nsManager = new XmlNamespaceManager (doc.NameTable);
54                         nsManager.AddNamespace ("wsdl", MetaData.WsdlNamespace);
55                         nsManager.AddNamespace ("s", MetaData.SchemaNamespace);
56                         nsManager.AddNamespace ("suds", MetaData.SudsNamespace);
57                 
58                         if (outputDirectory == null) outputDirectory = Directory.GetCurrentDirectory();
59                         
60                         CodeFile mainFile = new CodeFile (outputDirectory);
61                         
62                         currentFile = mainFile;
63                         
64                         // Suds types
65                         
66                         sudsTypes = new Hashtable ();
67                         XmlNodeList nodes = doc.DocumentElement.SelectNodes ("wsdl:binding/suds:class|wsdl:binding/suds:interface|wsdl:binding/suds:struct", nsManager);
68                         foreach (XmlElement node in nodes)
69                                 sudsTypes [GetTypeQualifiedName (node, node.GetAttribute ("type"))] = node;
70                         
71                         // Data types
72                         
73                         nodes = doc.SelectNodes ("wsdl:definitions/wsdl:types/s:schema", nsManager);
74                         foreach (XmlElement schema in nodes)
75                                 GenerateSchemaCode (schema);
76                         
77                         // Services
78                         
79                         nodes = doc.SelectNodes ("wsdl:definitions/wsdl:service/wsdl:port", nsManager);
80                         foreach (XmlElement port in nodes)
81                                 GeneratePortCode (port);
82                         
83                         mainFile.Write ();
84                         if (mainFile.FileName != null)
85                                 outCodeStreamList.Add (mainFile.FilePath);
86                 }
87                 
88                 void GeneratePortCode (XmlElement port)
89                 {
90                         XmlElement binding = GetBinding (port.GetAttribute ("binding"));
91                         XmlElement type = null;
92                         foreach (XmlNode node in binding)
93                                 if ((node is XmlElement) && ((XmlElement)node).NamespaceURI == MetaData.SudsNamespace) 
94                                 { type = (XmlElement) node; break; }
95                                         
96                         string rootType = type.GetAttribute ("rootType");
97                         if (rootType == "Delegate")
98                                 GenerateServiceDelegateCode (port, binding, type);
99                         else
100                                 GenerateServiceClassCode (port, binding, type);
101                 }
102                 
103                 void GenerateServiceDelegateCode (XmlElement port, XmlElement binding, XmlElement type)
104                 {
105                         string typeName = (type != null) ? type.GetAttribute ("type") : port.GetAttribute ("name");
106                         string portName = GetNameFromQn (binding.GetAttribute ("type"));
107                         
108                         string name, ns;
109                         GetTypeQualifiedName (port, typeName, out name, out ns);
110                         currentFile.SetCurrentNamespace (ns);
111                         
112                         XmlElement oper = (XmlElement) binding.SelectSingleNode ("wsdl:operation[@name='Invoke']", nsManager);
113                         if (oper == null) throw new InvalidOperationException ("Invalid delegate schema");
114                         
115                         string parsDec;
116                         string returnType;
117                         GetParameters (oper, portName, "Invoke", out parsDec, out returnType);
118
119                         currentFile.WriteLine ("public delegate " + returnType + " " + name + " (" + parsDec + ");");
120                         currentFile.WriteLine ("");
121                 }
122                 
123                 void GenerateServiceClassCode (XmlElement port, XmlElement binding, XmlElement type)
124                 {
125                         string typeName = (type != null) ? type.GetAttribute ("type") : port.GetAttribute ("name");
126                         
127                         string name, ns;
128                         GetTypeQualifiedName (port, typeName, out name, out ns);
129                         currentFile.SetCurrentNamespace (ns);
130                         
131                         string cls = "public " + type.LocalName + " " + name;
132                         string baset = type.GetAttribute ("extends");
133                         if (baset != "") cls += ": " + GetTypeQualifiedName (port, baset);
134                         
135                         // Interfaces
136                         
137                         XmlNodeList interfaces = type.SelectNodes ("suds:implements",nsManager);
138                         if (interfaces.Count == 0) interfaces = type.SelectNodes ("suds:extends",nsManager);
139                         
140                         foreach (XmlElement interf in interfaces)
141                         {
142                                 string iname = GetTypeQualifiedName (interf, interf.GetAttribute ("type"));
143                                 if (cls.IndexOf (':') == -1)  cls += ": " + iname;
144                                 else cls += ", " + iname;
145                         }
146                         
147                         currentFile.WriteLine (cls);
148                         currentFile.WriteLineInd ("{");
149                         
150                         string portName = GetNameFromQn (binding.GetAttribute ("type"));
151                         bool isInterface = type.LocalName == "interface";
152                         
153                         string vis = isInterface? "":"public ";
154                         
155                         ArrayList mets = GetMethods (portName, binding);
156                         foreach (MethodData met in mets)
157                         {
158                                 if (met.IsProperty)
159                                 {
160                                         string prop = vis + met.ReturnType + " ";
161                                         if (met.Signature != "") prop += "this [" + met.Signature + "]";
162                                         else prop += met.Name;
163                                         
164                                         if (isInterface)
165                                         {
166                                                 prop += " { ";
167                                                 if (met.HasGet) prop += "get; ";
168                                                 if (met.HasSet) prop += "set; ";
169                                                 prop += "}";
170                                                 currentFile.WriteLine (prop);
171                                         }
172                                         else
173                                         {
174                                                 currentFile.WriteLine (prop);
175                                                 currentFile.WriteLineInd ("{");
176                                                 if (met.HasGet) currentFile.WriteLine ("get { throw new NotImplementedException (); }");
177                                                 if (met.HasSet) currentFile.WriteLine ("set { throw new NotImplementedException (); }");
178                                                 currentFile.WriteLineUni ("}");
179                                                 currentFile.WriteLine ("");
180                                         }
181                                 }
182                                 else
183                                 {
184                                         currentFile.WriteLine (vis + met.ReturnType + " " + met.Name + " (" + met.Signature + ")" + (isInterface?";":""));
185                                         if (!isInterface)
186                                         {
187                                                 currentFile.WriteLineInd ("{");
188                                                 currentFile.WriteLine ("throw new NotImplementedException ();");
189                                                 currentFile.WriteLineUni ("}");
190                                                 currentFile.WriteLine ("");
191                                         }
192                                 }
193                         }
194                         
195                         currentFile.WriteLineUni ("}");
196                         currentFile.WriteLine ("");
197                 }
198                 
199                 class MethodData
200                 {
201                         public string ReturnType;
202                         public string Signature;
203                         public string Name;
204                         public bool HasSet;
205                         public bool HasGet;
206                         
207                         public bool IsProperty { get { return HasGet || HasSet; } }
208                 }
209                 
210                 ArrayList GetMethods (string portName, XmlElement binding)
211                 {
212                         ArrayList mets = new ArrayList ();
213                         
214                         XmlNodeList nodes = binding.SelectNodes ("wsdl:operation", nsManager);
215                         foreach (XmlElement oper in nodes)
216                         {
217                                 MethodData md = new MethodData ();
218                                 md.Name = oper.GetAttribute ("name");
219                                 
220                                 GetParameters (oper, portName, md.Name, out md.Signature, out md.ReturnType);
221                                 
222                                 if (md.Name.StartsWith ("set_") || md.Name.StartsWith ("get_"))
223                                 {
224                                         string tmp = ", " + md.Signature;
225                                         if (tmp.IndexOf (", out ") == -1 && tmp.IndexOf (", ref ") == -1)
226                                         {
227                                                 bool isSet = md.Name[0]=='s';
228                                                 md.Name = md.Name.Substring (4);
229                                                 MethodData previousProp = null;
230                                                 
231                                                 foreach (MethodData fmd in mets)
232                                                         if (fmd.Name == md.Name && fmd.IsProperty)
233                                                                 previousProp = fmd;
234                                                                 
235                                                 if (previousProp != null) {
236                                                         if (isSet) previousProp.HasSet = true;
237                                                         else { previousProp.HasGet = true; previousProp.Signature = md.Signature; }
238                                                         continue;
239                                                 }
240                                                 else {
241                                                         if (isSet) { md.HasSet = true; md.Signature = ""; }
242                                                         else md.HasGet = true;
243                                                 }
244                                         }
245                                 }
246                                 
247                                 mets.Add (md);
248                         }
249                         return mets;
250                 }
251                 
252                 void GetParameters (XmlElement oper, string portName, string operName, out string signature, out string returnType)
253                 {
254                         returnType = null;
255                         
256                         XmlElement portType = (XmlElement) doc.SelectSingleNode ("wsdl:definitions/wsdl:portType[@name='" + portName + "']", nsManager);
257                         XmlElement portOper = (XmlElement) portType.SelectSingleNode ("wsdl:operation[@name='" + operName + "']", nsManager);
258                         string[] parNames = portOper.GetAttribute ("parameterOrder").Split (' ');
259                         
260                         XmlElement inPortMsg = (XmlElement) portOper.SelectSingleNode ("wsdl:input", nsManager);
261                         XmlElement inMsg = FindMessageFromPortMessage (inPortMsg);
262                         
263                         XmlElement outPortMsg = (XmlElement) portOper.SelectSingleNode ("wsdl:output", nsManager);
264                         XmlElement outMsg = FindMessageFromPortMessage (outPortMsg);
265
266                         string[] parameters;
267                         if (parNames [0] != "") parameters = new string [parNames.Length];
268                         else parameters = new string [0];
269                         
270                         foreach (XmlElement part in inMsg.SelectNodes ("wsdl:part",nsManager))
271                         {
272                                 int i = Array.IndexOf (parNames, part.GetAttribute ("name"));
273                                 string type = GetTypeQualifiedName (part, part.GetAttribute ("type"));
274                                 parameters [i] = type + " " + parNames [i];
275                         }
276                         
277                         foreach (XmlElement part in outMsg.SelectNodes ("wsdl:part",nsManager))
278                         {
279                                 string pn = part.GetAttribute ("name");
280                                 string type = GetTypeQualifiedName (part, part.GetAttribute ("type"));
281                                 
282                                 if (pn == "return") 
283                                         returnType = type;
284                                 else {
285                                         int i = Array.IndexOf (parNames, pn);
286                                         if (parameters [i] != null) parameters [i] = "ref " + parameters [i];
287                                         else parameters [i] = "out " + type + " " + pn;
288                                 }
289                         }
290                         
291                         signature = string.Join (", ", parameters);
292                         if (returnType == null) returnType = "void";
293                 }
294                 
295                 XmlElement FindMessageFromPortMessage (XmlElement portMsg)
296                 {
297                         string msgName = portMsg.GetAttribute ("message");
298                         msgName = GetNameFromQn (msgName);
299                         return (XmlElement) doc.SelectSingleNode ("wsdl:definitions/wsdl:message[@name='" + msgName + "']", nsManager);
300                 }
301                 
302                 void GenerateSchemaCode (XmlElement schema)
303                 {
304                         string ns = schema.GetAttribute ("targetNamespace");
305                         string clrNs = DecodeNamespace (ns);
306                         currentFile.SetCurrentNamespace (clrNs);
307                         
308                         foreach (XmlNode node in schema)
309                         {
310                                 XmlElement elem = node as XmlElement;
311                                 if (elem == null) continue;
312                                 
313                                 if (elem.LocalName == "complexType")
314                                         GenerateClassCode (ns, elem);
315                                 else if (elem.LocalName == "simpleType")
316                                         GenerateEnumCode (ns, elem);
317                         }
318                 }
319                 
320                 void GenerateClassCode (string ns, XmlElement elem)
321                 {
322                         if (elem.SelectSingleNode ("s:complexContent/s:restriction", nsManager) != null) return;
323                         string clrNs = DecodeNamespace (ns);
324                         string typeName = GetTypeName (elem.GetAttribute ("name"), ns);
325                         
326                         XmlElement sudsType = (XmlElement) sudsTypes [clrNs + "." + typeName];
327                         
328                         string typetype = "class";
329                         if (sudsType != null) typetype = sudsType.LocalName;
330                         
331                         currentFile.WriteLine ("[Serializable, SoapType (XmlNamespace = @\"" + ns + "\", XmlTypeNamespace = @\"" + ns + "\")]");
332                         
333                         string cls = "public " + typetype + " " + typeName;
334                         string baseType = elem.GetAttribute ("base");
335                         if (baseType != "") cls += ": " + GetTypeQualifiedName (elem, baseType);
336                         
337                         bool isSerializable = (sudsType.GetAttribute ("rootType") == "ISerializable");
338                         
339                         if (isSerializable)
340                         {
341                                 if (cls.IndexOf (':') == -1) cls += ": ";
342                                 else cls += ", ";
343                                 cls += "System.Runtime.Serialization.ISerializable";
344                         }
345                         
346                         currentFile.WriteLine (cls);
347                         currentFile.WriteLineInd ("{");
348                         
349                         XmlNodeList elems = elem.GetElementsByTagName ("element", MetaData.SchemaNamespace);
350                         foreach (XmlElement elemField in elems)
351                                 WriteField (elemField);
352                         
353                         elems = elem.GetElementsByTagName ("attribute", MetaData.SchemaNamespace);
354                         foreach (XmlElement elemField in elems)
355                                 WriteField (elemField);
356                         
357                         if (isSerializable)
358                         {
359                                 currentFile.WriteLine ("");
360                                 currentFile.WriteLine ("public " + typeName + " ()");
361                                 currentFile.WriteLineInd ("{");
362                                 currentFile.WriteLineUni ("}");
363                                 currentFile.WriteLine ("");
364                                 
365                                 currentFile.WriteLine ("public " + typeName + " (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)");
366                                 currentFile.WriteLineInd ("{");
367                                 currentFile.WriteLine ("throw new NotImplementedException ();");
368                                 currentFile.WriteLineUni ("}");
369                                 currentFile.WriteLine ("");
370                                 
371                                 currentFile.WriteLine ("public void GetObjectData (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)");
372                                 currentFile.WriteLineInd ("{");
373                                 currentFile.WriteLine ("throw new NotImplementedException ();");
374                                 currentFile.WriteLineUni ("}");
375                         }
376                         
377                         currentFile.WriteLineUni ("}");
378                         currentFile.WriteLine ("");
379                 }
380                 
381                 void WriteField (XmlElement elemField)
382                 {
383                         bool isAttr = elemField.LocalName == "attribute";
384                         
385                         string type = elemField.GetAttribute ("type");
386                         
387                         if (isAttr)
388                                 currentFile.WriteLine ("[SoapField (UseAttribute = true)]");
389                         else if (!IsPrimitive (elemField, type))
390                                 currentFile.WriteLine ("[SoapField (Embedded = true)]");
391                         currentFile.WriteLine ("public " + GetTypeQualifiedName (elemField, type) + " " + elemField.GetAttribute ("name") + ";");
392                 }
393                 
394                 void GenerateEnumCode (string ns, XmlElement elem)
395                 {
396                         currentFile.WriteLine ("public enum " + GetTypeName (elem.GetAttribute ("name"), ns));
397                         currentFile.WriteLineInd ("{");
398                         
399                         XmlNodeList nodes = elem.SelectNodes ("s:restriction/s:enumeration/@value", nsManager);
400                         foreach (XmlNode node in nodes)
401                                 currentFile.WriteLine (node.Value + ",");
402                                 
403                         currentFile.WriteLineUni ("}");
404                         currentFile.WriteLine ("");
405                 }
406                 
407                 bool IsPrimitive (XmlNode node, string qname)
408                 {
409                         string name = GetTypeQualifiedName (node, qname);
410                         return name.IndexOf ('.') == -1;
411                 }
412                 
413                 string GetTypeName (string localName, string ns)
414                 {
415                         return localName;
416                 }
417                 
418                 void GetTypeQualifiedName (XmlNode node, string qualifiedName, out string name, out string ns)
419                 {
420                         int i = qualifiedName.IndexOf (':');
421                         if (i == -1)
422                         {
423                                 name = qualifiedName;
424                                 ns = "";
425                                 return;
426                         }
427                         
428                         string prefix = qualifiedName.Substring (0,i);
429                         name = qualifiedName.Substring (i+1);
430                         ns = node.GetNamespaceOfPrefix (prefix);
431                         
432                         string arrayType = GetArrayType (node, name, ns);
433                         if (arrayType != null) {
434                                 name = arrayType;
435                                 ns = "";
436                         }
437                         else if (ns != MetaData.SchemaNamespace) {
438                                 ns = DecodeNamespace (ns);
439                         }
440                         else {
441                                 ns = "";
442                                 name = GetClrFromXsd (name);
443                         }
444                 }
445                 
446                 string GetClrFromXsd (string type)
447                 {
448                         switch (type)
449                         {
450                                 case "boolean": return "bool";
451                                 case "unsignedByte": return "byte";
452                                 case "char": return "char";
453                                 case "dateTime": return "DateTime";
454                                 case "decimal": return "decimal";
455                                 case "double": return "double";
456                                 case "short": return "short";
457                                 case "int": return "int";
458                                 case "long": return "long";
459                                 case "byte": return "sbyte";
460                                 case "float": return "float";
461                                 case "unsignedShort": return "ushort";
462                                 case "unsignedInt": return "uint";
463                                 case "unsignedLong": return "ulong";
464                                 case "string": return "string";
465                                 case "duration": return "TimeSpan";
466                                 case "anyType": return "object";
467                         }
468                         throw new InvalidOperationException ("Unknown schema type: " + type);
469                 }
470                 
471                 string GetTypeQualifiedName (XmlNode node, string qualifiedName)
472                 {
473                         string name, ns;
474                         GetTypeQualifiedName (node, qualifiedName, out name, out ns);
475                         if (ns != "") return ns + "." + name;
476                         else return name;
477                 }
478                 
479                 string GetTypeNamespace (XmlNode node, string qualifiedName)
480                 {
481                         string name, ns;
482                         GetTypeQualifiedName (node, qualifiedName, out name, out ns);
483                         return ns;
484                 }
485                 
486                 string GetArrayType (XmlNode node, string name, string ns)
487                 {
488                         XmlNode anod = doc.SelectSingleNode ("wsdl:definitions/wsdl:types/s:schema[@targetNamespace='" + ns + "']/s:complexType[@name='" + name + "']/s:complexContent/s:restriction/s:attribute/@wsdl:arrayType", nsManager);
489                         if (anod == null) return null;
490                         
491                         string atype = anod.Value;
492                         int i = atype.IndexOf ('[');
493                         string itemType = GetTypeQualifiedName (node, atype.Substring (0,i));
494                         
495                         return itemType + atype.Substring (i);
496                 }
497                 
498                 XmlElement GetBinding (string name)
499                 {
500                         int i = name.IndexOf (':');
501                         name = name.Substring (i+1);
502                         return doc.SelectSingleNode ("wsdl:definitions/wsdl:binding[@name='" + name + "']", nsManager) as XmlElement;
503                 }
504                 
505                 string DecodeNamespace (string xmlNamespace)
506                 {
507                         string tns, tasm;
508                         
509                         if (!SoapServices.DecodeXmlNamespaceForClrTypeNamespace (xmlNamespace, out tns, out tasm))
510                                 tns = xmlNamespace;
511                                 
512                         return tns;
513                 }
514                 
515                 string GetLiteral (object ob)
516                 {
517                         if (ob == null) return "null";
518                         if (ob is string) return "\"" + ob.ToString().Replace("\"","\"\"") + "\"";
519                         if (ob is bool) return ((bool)ob) ? "true" : "false";
520                         if (ob is XmlQualifiedName) {
521                                 XmlQualifiedName qn = (XmlQualifiedName)ob;
522                                 return "new XmlQualifiedName (" + GetLiteral(qn.Name) + "," + GetLiteral(qn.Namespace) + ")";
523                         }
524                         else return ob.ToString ();
525                 }
526                 
527                 string Params (params string[] pars)
528                 {
529                         string res = "";
530                         foreach (string p in pars)
531                         {
532                                 if (res != "") res += ", ";
533                                 res += p;
534                         }
535                         return res;
536                 }
537                 
538                 string GetNameFromQn (string qn)
539                 {
540                         int i = qn.IndexOf (':');
541                         if (i == -1) return qn;
542                         else return qn.Substring (i+1);
543                 }
544         }
545         
546         class CodeFile
547         {
548                 public string FileName;
549                 public string Directory;
550                 public string FilePath;
551                 Hashtable namespaces = new Hashtable ();
552                 public StringWriter writer;
553                 int indent;
554
555                 public CodeFile (string directory)
556                 {
557                         Directory = directory;
558                 }
559                 
560                 public void SetCurrentNamespace (string ns)
561                 {
562                         writer = namespaces [ns] as StringWriter;
563                         if (writer == null)
564                         {
565                                 indent = 0;
566                                 writer = new StringWriter ();
567                                 namespaces [ns] = writer;
568                                 WriteLine ("namespace " + ns);
569                                 WriteLineInd ("{");
570                         }
571                         
572                         indent = 1;
573                         
574                         if (FileName == null)
575                                 FileName = ns + ".cs";
576                 }
577                 
578                 public void WriteLineInd (string code)
579                 {
580                         WriteLine (code);
581                         indent++;
582                 }
583                 
584                 public void WriteLineUni (string code)
585                 {
586                         if (indent > 0) indent--;
587                         WriteLine (code);
588                 }
589                 
590                 public void WriteLine (string code)
591                 {
592                         if (code != "") writer.Write (new String ('\t',indent));
593                         writer.WriteLine (code);
594                 }
595                 
596                 public void Write ()
597                 {
598                         if (FileName == null) return;
599                         
600                         FilePath = Path.Combine (Directory, FileName);
601                         StreamWriter sw = new StreamWriter (FilePath);
602                         
603                         sw.WriteLine ("using System;");
604                         sw.WriteLine ("using System.Runtime.Remoting.Metadata;");
605                         sw.WriteLine ();
606                         
607                         foreach (StringWriter nsWriter in namespaces.Values)
608                         {
609                                 sw.Write (nsWriter.ToString ());
610                                 sw.WriteLine ("}");
611                                 sw.WriteLine ();
612                         }
613                                 
614                         sw.Close ();
615                 }
616         }
617         
618         
619 }
620