2006-08-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Runtime.Serialization.Formatters.Soap / System.Runtime.Serialization.Formatters.Soap / SoapTypeMapper.cs
1 // created on 09/04/2003 at 18:58
2 //
3 //      System.Runtime.Serialization.Formatters.Soap.SoapTypeMapper
4 //
5 //      Authors:
6 //              Jean-Marc Andre (jean-marc.andre@polymtl.ca)
7 //
8
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Reflection;
32 using System.Collections;
33 using System.Runtime.Remoting;
34 using System.Xml;
35 using System.Xml.Serialization;
36 using System.Runtime.Serialization.Formatters;
37 using System.Xml.Schema;
38 using System.Runtime.Remoting.Metadata.W3cXsd2001;
39 using System.Globalization;
40 using System.Text;
41
42 namespace System.Runtime.Serialization.Formatters.Soap {
43
44         internal class Element
45         {
46                 private string _prefix;
47                 private string _localName;
48                 private string _namespaceURI;
49                 private MethodInfo _parseMethod;
50
51                 public Element(string prefix, string localName, string namespaceURI) 
52                 {
53                         _prefix = prefix;
54                         _localName = localName;
55                         _namespaceURI = namespaceURI;
56                 }
57
58                 public Element(string localName, string namespaceURI): this(null, localName, namespaceURI)
59                 {
60                 }
61
62                 public string Prefix
63                 {
64                         get
65                         {
66                                 return _prefix;
67                         }
68                 }
69
70                 public string LocalName
71                 {
72                         get
73                         {
74                                 return _localName;
75                         }
76                 }
77
78                 public string NamespaceURI 
79                 {
80                         get 
81                         {
82                                 return _namespaceURI;
83                         }
84                 }
85
86                 public MethodInfo ParseMethod {
87                         get { return _parseMethod; }
88                         set { _parseMethod = value; }
89                 }
90
91                 public override bool Equals(object obj) 
92                 {
93                         Element element = obj as Element;
94                         return (_localName == XmlConvert.DecodeName(element._localName) &&
95                                 _namespaceURI == XmlConvert.DecodeName(element._namespaceURI))?true:false;
96                 }
97
98                 public override int GetHashCode()
99                 {
100                         return (String.Format("{0} {1}", 
101                                 XmlConvert.DecodeName(_localName),
102                                 XmlConvert.DecodeName(_namespaceURI))).GetHashCode();
103                 }
104
105                 public override string ToString() 
106                 {
107                         return string.Format("Element.Prefix = {0}, Element.LocalName = {1}, Element.NamespaceURI = {2}", this.Prefix, this.LocalName, this.NamespaceURI);
108                 }
109         }
110
111         internal class SoapTypeMapper
112         {
113                 private static Hashtable xmlNodeToTypeTable = new Hashtable();
114                 private static Hashtable typeToXmlNodeTable = new Hashtable();
115                 public static readonly string SoapEncodingNamespace = "http://schemas.xmlsoap.org/soap/encoding/";
116                 public static readonly string SoapEncodingPrefix = "SOAP-ENC";
117                 public static readonly string SoapEnvelopeNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
118                 public static readonly string SoapEnvelopePrefix = "SOAP-ENV";
119                 //internal static readonly string SoapEnvelope;
120                 private XmlTextWriter _xmlWriter;
121                 private long _prefixNumber;
122                 private Hashtable namespaceToPrefixTable = new Hashtable();
123                 private SerializationBinder _binder;
124                 private static ArrayList _canBeValueTypeList;
125                 private FormatterAssemblyStyle _assemblyFormat = FormatterAssemblyStyle.Full;
126                 private Element elementString;
127
128
129                 // Constructor used by SoapReader
130                 public SoapTypeMapper(SerializationBinder binder) 
131                 {
132                         _binder = binder;
133                 }
134
135                 // Constructor used by SoapWriter
136                 public SoapTypeMapper(
137                         XmlTextWriter xmlWriter, 
138                         FormatterAssemblyStyle assemblyFormat,
139                         FormatterTypeStyle typeFormat) 
140                 {
141                         _xmlWriter = xmlWriter;
142                         _assemblyFormat = assemblyFormat;
143                         _prefixNumber = 1;
144                         //Type elementType = typeof(string);
145                         if(typeFormat == FormatterTypeStyle.XsdString)
146                         {
147                                 elementString = new Element("xsd", "string", XmlSchema.Namespace);
148                         }
149                         else
150                         {
151                                 elementString = new Element(SoapEncodingPrefix, "string", SoapEncodingNamespace);
152                         }
153 //                      typeToXmlNodeTable.Add(elementType.AssemblyQualifiedName, element);
154                 }
155                 
156                 static SoapTypeMapper() {
157 //                      SoapEnvelope = String.Format(
158 //                              "<{0}:Envelope xmlns:{0}='{1}' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='{2}' xmlns:{3}='{4}' xmlns:clr='{5}' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>",
159 //                              SoapEnvelopePrefix,
160 //                              SoapEnvelopeNamespace,
161 //                              XmlSchema.Namespace,
162 //                              SoapEncodingPrefix,
163 //                              SoapEncodingNamespace,
164 //                              SoapServices.XmlNsForClrType);
165                         _canBeValueTypeList = new ArrayList();
166                         _canBeValueTypeList.Add(typeof(DateTime).ToString());
167                         _canBeValueTypeList.Add(typeof(TimeSpan).ToString());
168                         _canBeValueTypeList.Add(typeof(string).ToString());
169                         _canBeValueTypeList.Add(typeof(decimal).ToString());
170                         _canBeValueTypeList.Sort();
171                         InitMappingTables();
172                         
173                 }
174                 
175                 static string GetKey (string localName, string namespaceUri)
176                 {
177                         return localName + " " +  namespaceUri;
178                 }
179                 
180 /*              public Type this [Element element]
181                 {
182                 }
183 */
184                 public Type GetType (string xmlName, string xmlNamespace)
185                 {
186                         Type type = null;
187
188                         string localName = XmlConvert.DecodeName (xmlName);
189                         string namespaceURI = XmlConvert.DecodeName (xmlNamespace);
190                         string typeNamespace, assemblyName;
191                         
192                         SoapServices.DecodeXmlNamespaceForClrTypeNamespace(
193                                 xmlNamespace, 
194                                 out typeNamespace, 
195                                 out assemblyName);
196
197                         string typeName = typeNamespace + Type.Delimiter + localName;
198
199                         if(assemblyName != null && assemblyName != string.Empty && _binder != null) 
200                         {
201                                 type = _binder.BindToType(assemblyName, typeName);
202                         }
203                         if(type == null) 
204                         {
205                                 string assemblyQualifiedName = (string)xmlNodeToTypeTable [GetKey (xmlName, xmlNamespace)];
206                                 if(assemblyQualifiedName != null)
207                                         type = Type.GetType(assemblyQualifiedName);
208                                 else
209                                 {
210                                         type = Type.GetType(xmlName);
211                                         if(type == null) 
212                                         { 
213
214                                                 type = Type.GetType(typeName);
215                                                 if(type == null) 
216                                                 {
217
218                                                         if(assemblyName == null || assemblyName == String.Empty)
219                                                                 throw new SerializationException(
220                                                                         String.Format("Parse Error, no assembly associated with XML key {0} {1}", 
221                                                                         localName, 
222                                                                         namespaceURI));
223                                                         type = FormatterServices.GetTypeFromAssembly(
224                                                                 Assembly.Load(assemblyName), 
225                                                                 typeName);
226                                                 }
227                                         }
228                                 }
229                                 if(type == null)
230                                         throw new SerializationException();
231                         }
232                         return type;
233                 }
234
235                 public Element GetXmlElement (string typeFullName, string assemblyName)
236                 {
237                         Element element;
238                         string typeNamespace = string.Empty;
239                         string typeName = typeFullName;
240                         if(_assemblyFormat == FormatterAssemblyStyle.Simple)
241                         {
242                                 string[] items = assemblyName.Split(',');
243                                 assemblyName = items[0];
244                         }
245                         string assemblyQualifiedName = typeFullName + ", " + assemblyName;
246                         element = (Element) typeToXmlNodeTable[assemblyQualifiedName];
247                         if(element == null)
248                         {
249                                 int typeNameIndex = typeFullName.LastIndexOf('.');
250                                 if(typeNameIndex != -1) 
251                                 {
252                                         typeNamespace = typeFullName.Substring(0, typeNameIndex);
253                                         typeName = typeFullName.Substring(typeNamespace.Length + 1);
254                                 }
255                                 string namespaceURI = 
256                                         SoapServices.CodeXmlNamespaceForClrTypeNamespace(
257                                         typeNamespace, 
258                                         (!assemblyName.StartsWith("mscorlib"))?assemblyName:String.Empty);
259                                 string prefix = (string) namespaceToPrefixTable[namespaceURI];
260                                 if(prefix == null || prefix == string.Empty)
261                                 {
262                                         prefix = "a" + (_prefixNumber++).ToString();
263                                         namespaceToPrefixTable[namespaceURI] = prefix;
264
265                                 }
266
267                                 int i = typeName.IndexOf ("[");
268                                 if (i != -1)
269                                         typeName = XmlConvert.EncodeName (typeName.Substring (0, i)) + typeName.Substring (i);
270                                 else
271                                 {
272                                         int j = typeName.IndexOf ("&");
273                                         if (j != -1)
274                                                 typeName = XmlConvert.EncodeName (typeName.Substring (0, j)) + typeName.Substring (j);
275                                         else
276                                                 typeName = XmlConvert.EncodeName (typeName);
277                                 }
278
279                                 element = new Element(
280                                         prefix, 
281                                         typeName, 
282                                         namespaceURI);
283                         }
284                         return element;
285                 }
286
287                 public Element GetXmlElement (Type type)
288                 {
289                         if(type == typeof(string)) return elementString;
290                         Element element = (Element) typeToXmlNodeTable[type.AssemblyQualifiedName];
291                         if(element == null)
292                         {
293                                 element = GetXmlElement (type.FullName, type.Assembly.FullName);
294 //                                      if(_assemblyFormat == FormatterAssemblyStyle.Full)
295 //                                              element = this[type.FullName, type.Assembly.FullName];
296 //                                      else
297 //                                              element = this[type.FullName, type.Assembly.GetName().Name];
298
299                         }
300                         else if (_xmlWriter != null)
301                         {
302                                 element = new Element((element.Prefix == null)?_xmlWriter.LookupPrefix(element.NamespaceURI):element.Prefix, element.LocalName, element.NamespaceURI);
303                         }
304                         if(element == null)
305                                 throw new SerializationException("Oooops");
306                         return element;
307                 }
308
309                 static void RegisterType (Type type, string name, string namspace)
310                 {
311                         RegisterType (type, name, namspace, true);
312                 }
313                 
314                 static Element RegisterType (Type type, string name, string namspace, bool reverseMap)
315                 {
316                         Element element = new Element (name, namspace);
317                         xmlNodeToTypeTable.Add (GetKey (name, namspace), type.AssemblyQualifiedName);
318                         if (reverseMap)
319                                 typeToXmlNodeTable.Add (type.AssemblyQualifiedName, element);
320                         return element;
321                 }
322                 
323                 static void RegisterType (Type type)
324                 {
325                         string name = (string) type.GetProperty ("XsdType", BindingFlags.Public | BindingFlags.Static).GetValue (null, null);
326                         Element element = RegisterType (type, name, XmlSchema.Namespace, true);
327                         element.ParseMethod = type.GetMethod ("Parse", BindingFlags.Public | BindingFlags.Static);
328                         if (element.ParseMethod == null)
329                                 throw new InvalidOperationException ("Parse method not found in class " + type);
330                 }
331
332                 private static void InitMappingTables() 
333                 {
334                         RegisterType (typeof(System.Array), "Array", SoapEncodingNamespace);
335                         RegisterType (typeof(string), "string", XmlSchema.Namespace, false);
336                         RegisterType (typeof(string), "string", SoapEncodingNamespace, false);
337                         RegisterType (typeof(bool), "boolean", XmlSchema.Namespace);
338                         RegisterType (typeof(sbyte), "byte", XmlSchema.Namespace);
339                         RegisterType (typeof(byte), "unsignedByte", XmlSchema.Namespace);
340                         RegisterType (typeof(long), "long", XmlSchema.Namespace);
341                         RegisterType (typeof(ulong), "unsignedLong", XmlSchema.Namespace);
342                         RegisterType (typeof(int), "int", XmlSchema.Namespace);
343                         RegisterType (typeof(uint), "unsignedInt", XmlSchema.Namespace);
344                         RegisterType (typeof(float), "float", XmlSchema.Namespace);
345                         RegisterType (typeof(double), "double", XmlSchema.Namespace);
346                         RegisterType (typeof(decimal), "decimal", XmlSchema.Namespace);
347                         RegisterType (typeof(short), "short", XmlSchema.Namespace);
348                         RegisterType (typeof(ushort), "unsignedShort", XmlSchema.Namespace);
349                         RegisterType (typeof(object), "anyType", XmlSchema.Namespace);
350                         RegisterType (typeof(DateTime), "dateTime", XmlSchema.Namespace);
351                         RegisterType (typeof(TimeSpan), "duration", XmlSchema.Namespace);
352                         RegisterType (typeof(SoapFault), "Fault", SoapEnvelopeNamespace);
353                         RegisterType (typeof(byte[]), "base64", SoapEncodingNamespace);
354                         RegisterType (typeof(MethodSignature), "methodSignature", SoapEncodingNamespace);
355                         RegisterType (typeof(SoapAnyUri));
356                         RegisterType (typeof(SoapEntity));
357                         RegisterType (typeof(SoapMonth));
358                         RegisterType (typeof(SoapNonNegativeInteger));
359                         RegisterType (typeof(SoapToken));
360                         RegisterType (typeof(SoapBase64Binary));
361                         RegisterType (typeof(SoapHexBinary));
362                         RegisterType (typeof(SoapMonthDay));
363                         RegisterType (typeof(SoapNonPositiveInteger));
364                         RegisterType (typeof(SoapYear));
365                         RegisterType (typeof(SoapDate));
366                         RegisterType (typeof(SoapId));
367                         RegisterType (typeof(SoapName));
368                         RegisterType (typeof(SoapNormalizedString));
369                         RegisterType (typeof(SoapYearMonth));
370                         RegisterType (typeof(SoapIdref));
371                         RegisterType (typeof(SoapNcName));
372                         RegisterType (typeof(SoapNotation));
373                         RegisterType (typeof(SoapDay));
374                         RegisterType (typeof(SoapIdrefs));
375                         RegisterType (typeof(SoapNegativeInteger));
376                         RegisterType (typeof(SoapPositiveInteger));
377                         RegisterType (typeof(SoapInteger));
378                         RegisterType (typeof(SoapNmtoken));
379                         RegisterType (typeof(SoapQName));
380                         RegisterType (typeof(SoapEntities));
381                         RegisterType (typeof(SoapLanguage));
382                         RegisterType (typeof(SoapNmtokens));
383                         RegisterType (typeof(SoapTime));
384                 }
385                 
386                 public static string GetXsdValue (object value)
387                 {
388                         if (value is DateTime) {
389                                 return SoapDateTime.ToString ((DateTime)value);
390                         }
391                         else if (value is decimal) {
392                                 return ((decimal) value).ToString (CultureInfo.InvariantCulture);
393                         }
394                         else if (value is double) {
395                                 return ((double) value).ToString ("G17", CultureInfo.InvariantCulture);
396                         }
397                         else if (value is float) {
398                                 return ((float) value).ToString ("G9", CultureInfo.InvariantCulture);
399                         }
400                         else if (value is TimeSpan) {
401                                 return SoapDuration.ToString ((TimeSpan)value);
402                         }
403                         else if (value is bool) {
404                                 return ((bool) value) ? "true" : "false";
405                         }
406                         else if (value is MethodSignature) {
407                                 return null;
408                         }
409                         else {
410                                 return value.ToString ();
411                         }
412                 }
413                 
414                 public static object ParseXsdValue (string value, Type type)
415                 {
416                         if (type == typeof(DateTime)) {
417                                 return SoapDateTime.Parse (value);
418                         }
419                         else if (type == typeof(decimal)) {
420                                 return decimal.Parse (value, CultureInfo.InvariantCulture);
421                         }
422                         else if (type == typeof(double)) {
423                                 return double.Parse (value, CultureInfo.InvariantCulture);
424                         }
425                         else if (type == typeof(float)) {
426                                 return float.Parse (value, CultureInfo.InvariantCulture);
427                         }
428                         else if (type == typeof (TimeSpan)) {
429                                 return SoapDuration.Parse (value);
430                         }
431                         else if(type.IsEnum) {
432                                 return Enum.Parse(type, value);
433                         }
434                         else {
435                                 return Convert.ChangeType (value, type, CultureInfo.InvariantCulture);
436                         }
437                 }
438                 
439                 public static bool CanBeValue (Type type)
440                 {
441                         if(type.IsPrimitive) return true;
442                         if(type.IsEnum) return true;
443                         if(_canBeValueTypeList.BinarySearch(type.ToString()) >= 0) 
444                         {
445                                 return true;
446                         }
447                         return false;
448                 }
449                 
450                 public bool IsInternalSoapType (Type type)
451                 {
452                         if (CanBeValue (type))
453                                 return true;
454                         if (typeof(ISoapXsd).IsAssignableFrom (type))
455                                 return true;
456                         if (type == typeof (MethodSignature))
457                                 return true;
458                         return false;
459                 }
460                 
461                 public object ReadInternalSoapValue (SoapReader reader, Type type)
462                 {
463                         if (CanBeValue (type))
464                                 return ParseXsdValue (reader.XmlReader.ReadElementString (), type);
465                         
466                         if (type == typeof(MethodSignature)) {
467                                 return MethodSignature.ReadXmlValue (reader);
468                         }
469                         
470                         string val = reader.XmlReader.ReadElementString ();
471                         
472                         Element elem = GetXmlElement (type);
473                         if (elem.ParseMethod != null)
474                                 return elem.ParseMethod.Invoke (null, new object[] { val });
475                         
476                         throw new SerializationException ("Can't parse type " + type);
477                 }
478                 
479                 public string GetInternalSoapValue (SoapWriter writer, object value)
480                 {
481                         if (CanBeValue (value.GetType()))
482                                 return GetXsdValue (value);
483                         else if (value is MethodSignature)
484                                 return ((MethodSignature)value).GetXmlValue (writer);
485                         else
486                                 return value.ToString ();
487                 }
488         }
489         
490         class MethodSignature
491         {
492                 Type[] types;
493                 
494                 public MethodSignature (Type[] types)
495                 {
496                         this.types = types;
497                 }
498                 
499                 public static object ReadXmlValue (SoapReader reader)
500                 {
501                         reader.XmlReader.MoveToElement ();
502                         if (reader.XmlReader.IsEmptyElement) {
503                                 reader.XmlReader.Skip ();
504                                 return new Type[0];
505                         }
506                         reader.XmlReader.ReadStartElement ();
507                         string names = reader.XmlReader.ReadString ();
508                         while (reader.XmlReader.NodeType != XmlNodeType.EndElement)
509                                 reader.XmlReader.Skip ();
510                                 
511                         ArrayList types = new ArrayList ();
512                         string[] tns = names.Split (' ');
513                         foreach (string tn in tns) {
514                                 if (tn.Length == 0) continue;
515                                 types.Add (reader.GetTypeFromQName (tn));
516                         }
517                         reader.XmlReader.ReadEndElement ();
518                         return (Type[]) types.ToArray (typeof(Type));
519                 }
520                 
521                 public string GetXmlValue (SoapWriter writer)
522                 {
523                         StringBuilder sb = new StringBuilder ();
524                         foreach (Type t in types) {
525                                 Element elem = writer.Mapper.GetXmlElement (t);
526                                 if (sb.Length > 0) sb.Append (' ');
527                                 string prefix = writer.GetNamespacePrefix (elem);
528                                 sb.Append (prefix).Append (':').Append (elem.LocalName);
529                         }
530                         return sb.ToString ();
531                 }
532         }
533 }