2010-04-15 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.Runtime.Serialization / System.Runtime.Serialization / XsdDataContractExporter-new.cs
blob27bde112d6423ac993fd8a20d11a67480cc03cbd
1 //
2 // XsdDataContractExporter.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2010 Novell, Inc. http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.CodeDom;
30 using System.CodeDom.Compiler;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Linq;
35 using System.Reflection;
36 using System.Xml;
37 using System.Xml.Schema;
38 using System.Xml.Serialization;
40 using QName = System.Xml.XmlQualifiedName;
43 // .NET exports almost empty schema for "http://www.w3.org/2001/XMLSchema" that
44 // contains only "schema" element which consists of a complexType with empty
45 // definition (i.e. <complexType/> ).
48 namespace System.Runtime.Serialization
50 static class TypeExtension
52 public static T GetCustomAttribute<T> (this MemberInfo mi, bool inherit)
54 foreach (T att in mi.GetCustomAttributes (typeof (T), inherit))
55 return att;
56 return default (T);
60 public class XsdDataContractExporter
62 class TypeImportInfo
64 public Type ClrType { get; set; }
65 public QName RootElementName { get; set; }
66 public XmlSchemaType SchemaType { get; set; }
67 public QName SchemaTypeName { get; set; }
70 static readonly List<TypeImportInfo> predefined_types;
72 static XsdDataContractExporter ()
74 var l = new List<TypeImportInfo> ();
75 predefined_types = l;
76 if (!MSTypesSchema.IsCompiled)
77 MSTypesSchema.Compile (null);
78 foreach (XmlSchemaElement el in MSTypesSchema.Elements.Values) {
79 var typeName = el.ElementSchemaType.QualifiedName;
80 var info = new TypeImportInfo () {
81 RootElementName = el.QualifiedName,
82 SchemaType = typeName.Namespace == XmlSchema.Namespace ? null : el.ElementSchemaType,
83 SchemaTypeName = typeName,
84 ClrType = GetPredefinedTypeFromQName (typeName) };
85 l.Add (info);
89 static Type GetPredefinedTypeFromQName (QName qname)
91 switch (qname.Namespace) {
92 case XmlSchema.Namespace:
93 return KnownTypeCollection.GetPrimitiveTypeFromName (qname.Name);
94 case KnownTypeCollection.MSSimpleNamespace:
95 switch (qname.Name) {
96 case "char":
97 return typeof (char);
98 case "duration":
99 return typeof (TimeSpan);
100 case "guid":
101 return typeof (Guid);
103 break;
105 throw new Exception ("Should not happen");
108 static XmlSchema mstypes_schema;
109 static XmlSchema MSTypesSchema {
110 get {
111 if (mstypes_schema == null) {
112 Assembly a = Assembly.GetCallingAssembly ();
113 Stream s = a.GetManifestResourceStream ("mstypes.schema");
114 mstypes_schema= XmlSchema.Read (s, null);
116 return mstypes_schema;
120 KnownTypeCollection known_types = new KnownTypeCollection ();
121 List<TypeImportInfo> imported_types = new List<TypeImportInfo> ();
123 public XsdDataContractExporter ()
124 : this (new XmlSchemaSet ())
128 public XsdDataContractExporter (XmlSchemaSet schemas)
130 if (schemas == null)
131 throw new ArgumentNullException ("schemas");
132 #if false // by default this is the only added schema. But it is pointless...
133 var xs = new XmlSchema () { TargetNamespace = XmlSchema.Namespace };
134 xs.Items.Add (new XmlSchemaElement () { Name = "schema", SchemaType = new XmlSchemaComplexType () });
135 schemas.Add (xs);
136 #else // FIXME: it is added only when the included items are in use.
137 schemas.Add (MSTypesSchema);
138 #endif
139 Schemas = schemas;
142 public ExportOptions Options { get; set; }
143 public XmlSchemaSet Schemas { get; private set; }
145 // CanExport implementation
147 public bool CanExport (ICollection<Assembly> assemblies)
149 if (assemblies == null)
150 throw new ArgumentNullException ("assemblies");
151 foreach (var ass in assemblies)
152 if (!CanExport (ass.GetTypes ()))
153 return false;
154 return true;
157 public bool CanExport (ICollection<Type> types)
159 if (types == null)
160 throw new ArgumentNullException ("types");
161 foreach (var type in types)
162 if (!CanExport (type))
163 return false;
164 return true;
167 public bool CanExport (Type type)
169 if (type == null)
170 throw new ArgumentNullException ("type");
172 if (predefined_types.FirstOrDefault (i => i.ClrType == type) != null)
173 return true;
175 known_types.TryRegister (type);
176 return known_types.FindUserMap (type) != null;
179 // Export implementation
181 public void Export (ICollection<Assembly> assemblies)
183 if (assemblies == null)
184 throw new ArgumentNullException ("assemblies");
185 foreach (var ass in assemblies)
186 Export (ass.GetTypes ());
189 public void Export (ICollection<Type> types)
191 if (types == null)
192 throw new ArgumentNullException ("types");
193 foreach (var type in types)
194 Export (type);
197 public void Export (Type type)
199 if (ExportCore (type, true)) {
200 // This reprocess is required to clean up compilation state.
201 foreach (XmlSchema xs in Schemas.Schemas ())
202 Schemas.Reprocess (xs);
203 Schemas.Compile ();
207 // returns true if it requires recompilcation
208 bool ExportCore (Type type, bool rejectNonContract)
210 if (type == null)
211 throw new ArgumentNullException ("type");
213 if (predefined_types.FirstOrDefault (i => i.ClrType == type) != null) {
214 if (Schemas.Contains (MSTypesSchema.TargetNamespace))
215 return false; // exists
216 Schemas.Add (MSTypesSchema);
217 return false;
219 if (imported_types.FirstOrDefault (i => i.ClrType == type) != null)
220 return false;
222 known_types.TryRegister (type);
223 var map = known_types.FindUserMap (type);
224 if (map == null)
225 return false;
226 map.ExportSchemaType (this);
227 return true;
230 internal void ExportDictionaryContractType (CollectionDataContractAttribute attr, Type type, Type dicType)
232 var qname = GetSchemaTypeName (type);
234 var typeArgs = dicType.IsGenericType ? dicType.GetGenericArguments () : null;
235 var keyType = typeArgs != null ? typeArgs [0] : typeof (object);
236 var valueType = typeArgs != null ? typeArgs [1] : typeof (object);
237 ExportCore (keyType, false);
238 ExportCore (valueType, false);
240 string keyName = "Key", valueName = "Value";
241 if (attr != null) {
242 keyName = attr.KeyName ?? keyName;
243 valueName = attr.ValueName ?? valueName;
245 string itemName = attr != null && attr.ItemName != null ? attr.ItemName : "KeyValueOf" + keyName + valueName;
247 var ct = CreateComplexType (qname, type);
248 var appInfo = new XmlSchemaAppInfo ();
249 var node = new XmlDocument ().CreateElement ("IsDictionary", KnownTypeCollection.MSSimpleNamespace);
250 node.InnerText = "true";
251 appInfo.Markup = new XmlNode [] { node };
252 ct.Annotation = new XmlSchemaAnnotation ();
253 ct.Annotation.Items.Add (appInfo);
255 var seq = new XmlSchemaSequence ();
256 ct.Particle = seq;
257 var el = new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded" };
258 seq.Items.Add (el);
260 var dictType = new XmlSchemaComplexType ();
261 el.SchemaType = dictType;
262 var dictSeq = new XmlSchemaSequence ();
263 dictType.Particle = dictSeq;
264 dictSeq.Items.Add (new XmlSchemaElement () { Name = keyName, SchemaTypeName = GetSchemaTypeName (keyType), IsNillable = true });
265 dictSeq.Items.Add (new XmlSchemaElement () { Name = valueName, SchemaTypeName = GetSchemaTypeName (valueType), IsNillable = true });
268 internal void ExportListContractType (CollectionDataContractAttribute attr, Type type)
270 var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type);
272 var typeArgs = type.IsGenericType ? type.GetGenericArguments () : null;
273 if (typeArgs != null && typeArgs.Length != 1)
274 throw new InvalidDataContractException ("CollectionDataContractAttribute is applied to non-collection type.");
276 var itemType = typeArgs != null ? typeArgs [0] : type.IsArray ? type.GetElementType () : typeof (object);
277 bool nullable = !itemType.IsValueType;
278 if (itemType.IsGenericType && itemType.GetGenericTypeDefinition () == typeof (Nullable<>)) {
279 itemType = itemType.GetGenericArguments () [0];
280 nullable = true;
282 ExportCore (itemType, false);
284 var itemQName = GetSchemaTypeName (itemType);
285 var itemName = attr != null && attr.ItemName != null ? attr.ItemName : itemQName.Name;
287 var ct = CreateComplexType (qname, type);
288 var seq = new XmlSchemaSequence ();
289 ct.Particle = seq;
290 var el = new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded", SchemaTypeName = itemQName, IsNillable = nullable };
291 seq.Items.Add (el);
294 var arrayType = new XmlSchemaComplexType ();
295 el.SchemaType = arrayType;
296 var arraySeq = new XmlSchemaSequence ();
297 arrayType.Particle = arraySeq;
298 arraySeq.Items.Add (new XmlSchemaElement () { Name = itemName, SchemaTypeName = itemQName, IsNillable = true });
302 internal void ExportEnumContractType (DataContractAttribute attr, Type type)
304 var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type);
305 var st = CreateSimpleType (qname, type);
306 if (type.GetCustomAttribute<FlagsAttribute> (false) != null) {
307 var list = new XmlSchemaSimpleTypeList ();
308 var sct = new XmlSchemaSimpleType ();
309 sct.Content = CreateEnumMembers (type, attr != null);
310 list.ItemType = sct;
311 st.Content = list;
313 else
314 st.Content = CreateEnumMembers (type, attr != null);
317 XmlSchemaSimpleTypeRestriction CreateEnumMembers (Type type, bool expectAttribute)
319 var r = new XmlSchemaSimpleTypeRestriction () { BaseTypeName = GetSchemaTypeName (typeof (string)) };
320 foreach (var mi in type.GetFields (BindingFlags.Public | BindingFlags.Static)) {
321 var ema = expectAttribute ? mi.GetCustomAttribute<EnumMemberAttribute> (false) : null;
322 if (expectAttribute && ema == null)
323 continue;
324 var xe = new XmlSchemaEnumerationFacet () { Value = ema != null && ema.Value != null ? ema.Value : mi.Name };
325 r.Facets.Add (xe);
327 return r;
330 internal void ExportStandardComplexType (DataContractAttribute attr, Type type, List<DataMemberInfo> members)
332 var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type);
333 var ct = CreateComplexType (qname, type);
335 if (type.BaseType != null && type.BaseType != typeof (object)) {
336 ExportCore (type.BaseType, false);
337 var xcc = new XmlSchemaComplexContent ();
338 ct.ContentModel = xcc;
339 var xcce = new XmlSchemaComplexContentExtension ();
340 xcc.Content = xcce;
341 xcce.BaseTypeName = GetSchemaTypeName (type.BaseType);
342 xcce.Particle = CreateMembersSequence (type, members, attr != null);
344 else
345 ct.Particle = CreateMembersSequence (type, members, attr != null);
348 XmlSchemaSimpleType CreateSimpleType (QName qname, Type type)
350 var xs = GetSchema (qname.Namespace);
352 var el = new XmlSchemaElement () { Name = qname.Name, IsNillable = true };
353 el.SchemaTypeName = qname;
354 xs.Items.Add (el);
355 var st = new XmlSchemaSimpleType () { Name = qname.Name };
356 xs.Items.Add (st);
357 imported_types.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = st, SchemaTypeName = qname, ClrType = type });
359 return st;
362 XmlSchemaComplexType CreateComplexType (QName qname, Type type)
364 var xs = GetSchema (qname.Namespace);
366 var el = new XmlSchemaElement () { Name = qname.Name, IsNillable = true };
367 el.SchemaTypeName = qname;
368 xs.Items.Add (el);
369 var ct = new XmlSchemaComplexType () { Name = qname.Name };
370 xs.Items.Add (ct);
371 imported_types.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = ct, SchemaTypeName = qname, ClrType = type });
373 return ct;
376 static int CompareMembers (MemberInfo m1, MemberInfo m2)
378 var a1 = m1.GetCustomAttribute<DataMemberAttribute> (false);
379 var a2 = m2.GetCustomAttribute<DataMemberAttribute> (false);
380 return a1.Order == a2.Order ? String.CompareOrdinal (a1.Name ?? m1.Name, a2.Name ?? m2.Name) : a1.Order - a2.Order;
383 // FIXME: use members parameter to determine which members are to be exported.
384 XmlSchemaSequence CreateMembersSequence (Type type, List<DataMemberInfo> dataMembers, bool expectContract)
386 var seq = new XmlSchemaSequence ();
387 var members = new List<MemberInfo> ();
388 var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
389 if (expectContract)
390 flags |= BindingFlags.NonPublic;
392 foreach (var mi in type.GetFields (flags))
393 if (!expectContract || mi.GetCustomAttribute<DataMemberAttribute> (false) != null)
394 members.Add (mi);
395 foreach (var mi in type.GetProperties (flags))
396 if ((!expectContract || mi.GetCustomAttribute<DataMemberAttribute> (false) != null) && mi.GetIndexParameters ().Length == 0)
397 members.Add (mi);
399 if (expectContract)
400 members.Sort (CompareMembers);
402 foreach (var mi in members) {
403 var dma = mi.GetCustomAttribute<DataMemberAttribute> (false);
404 var fi = mi as FieldInfo;
405 var pi = mi as PropertyInfo;
406 var mt = fi != null ? fi.FieldType : pi.PropertyType;
407 bool nullable = !mt.IsValueType;
408 if (mt.IsGenericType && mt.GetGenericTypeDefinition () == typeof (Nullable<>)) {
409 mt = mt.GetGenericArguments () [0];
410 nullable = true;
412 ExportCore (mt, false);
414 var name = dma != null && dma.Name != null ? dma.Name : mi.Name;
415 var xe = new XmlSchemaElement () { Name = name, IsNillable = nullable };
416 xe.SchemaTypeName = GetSchemaTypeName (mt);
417 seq.Items.Add (xe);
419 return seq;
422 XmlSchema GetSchema (string ns)
424 foreach (XmlSchema xs in Schemas.Schemas (ns))
425 return xs;
426 var nxs = new XmlSchema () { ElementFormDefault = XmlSchemaForm.Qualified };
427 if (!String.IsNullOrEmpty (ns))
428 nxs.TargetNamespace = ns;
429 Schemas.Add (nxs);
430 return nxs;
433 string GetXmlTypeName (Type type)
435 var qname = KnownTypeCollection.GetPrimitiveTypeName (type);
436 return qname.Equals (QName.Empty) ? type.Name : qname.Name;
439 string GetXmlNamespace (Type type)
441 foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), false))
442 if (a.ClrNamespace == type.Namespace)
443 return a.ContractNamespace;
444 return KnownTypeCollection.DefaultClrNamespaceBase + type.Namespace;
447 // get mapping info (either exported or predefined).
449 public QName GetRootElementName (Type type)
451 var info = predefined_types.FirstOrDefault (i => i.ClrType == type);
452 if (info != null)
453 return info.RootElementName;
454 info = imported_types.FirstOrDefault (i => i.ClrType == type);
455 if (info != null && info.RootElementName != null)
456 return info.RootElementName;
458 return GetSchemaTypeName (type);
461 public XmlSchemaType GetSchemaType (Type type)
463 var info = predefined_types.FirstOrDefault (i => i.ClrType == type);
464 if (info != null)
465 return info.SchemaType;
466 info = imported_types.FirstOrDefault (i => i.ClrType == type);
467 if (info != null)
468 return info.SchemaType;
470 return null;
473 public QName GetSchemaTypeName (Type type)
475 var info = predefined_types.FirstOrDefault (i => i.ClrType == type);
476 if (info != null)
477 return info.SchemaTypeName;
478 info = imported_types.FirstOrDefault (i => i.ClrType == type);
479 if (info != null && info.SchemaTypeName != null)
480 return info.SchemaTypeName;
482 var cdca = type.GetCustomAttribute<CollectionDataContractAttribute> (false);
483 if (cdca != null)
484 return new QName (cdca.Name ?? GetXmlTypeName (type), cdca.Namespace ?? GetXmlNamespace (type));
485 var dca = type.GetCustomAttribute<DataContractAttribute> (false);
486 if (dca != null)
487 return new QName (dca.Name ?? GetXmlTypeName (type), dca.Namespace ?? GetXmlNamespace (type));
489 if (type.IsArray) {
490 var item = GetSchemaTypeName (type.GetElementType ());
491 if (item.Namespace == XmlSchema.Namespace)
492 return new QName ("ArrayOf" + item.Name, KnownTypeCollection.MSArraysNamespace);
493 return new QName ("ArrayOf" + item.Name, item.Namespace);
496 return new QName (type.Name, KnownTypeCollection.DefaultClrNamespaceBase + type.Namespace);