2010-05-25 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.Xaml / System.Xaml / XamlObjectReader.cs
blobaa8c8d043f4495d85cb004aef99e2bdb35935fb2
1 //
2 // Copyright (C) 2010 Novell Inc. http://novell.com
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 using System;
24 using System.Collections;
25 using System.Collections.Generic;
26 using System.ComponentModel;
27 using System.Linq;
28 using System.Windows.Markup;
29 using System.Xaml.Schema;
31 namespace System.Xaml
33 public class XamlObjectReader : XamlReader
35 #region nested types
37 class NSList : List<NamespaceDeclaration>
39 public NSList (XamlNodeType ownerType, IEnumerable<NamespaceDeclaration> nsdecls)
40 : base (nsdecls)
42 OwnerType = ownerType;
45 public XamlNodeType OwnerType { get; set; }
47 public IEnumerator<NamespaceDeclaration> GetEnumerator ()
49 return new NSEnumerator (this, base.GetEnumerator ());
53 class NSEnumerator : IEnumerator<NamespaceDeclaration>
55 NSList list;
56 IEnumerator<NamespaceDeclaration> e;
58 public NSEnumerator (NSList list, IEnumerator<NamespaceDeclaration> e)
60 this.list= list;
61 this.e = e;
64 public XamlNodeType OwnerType {
65 get { return list.OwnerType; }
68 public void Dispose ()
72 public bool MoveNext ()
74 return e.MoveNext ();
77 public NamespaceDeclaration Current {
78 get { return e.Current; }
81 object IEnumerator.Current {
82 get { return Current; }
85 public void Reset ()
87 throw new NotSupportedException ();
91 class PrefixLookup : INamespacePrefixLookup
93 XamlObjectReader source;
95 public PrefixLookup (XamlObjectReader source)
97 this.source = source;
100 public string LookupPrefix (string ns)
102 return source.LookupPrefix (ns);
106 #endregion nested types
108 public XamlObjectReader (object instance)
109 : this (instance, new XamlSchemaContext (null, null), null)
113 public XamlObjectReader (object instance, XamlObjectReaderSettings settings)
114 : this (instance, new XamlSchemaContext (null, null), settings)
118 public XamlObjectReader (object instance, XamlSchemaContext schemaContext)
119 : this (instance, schemaContext, null)
123 public XamlObjectReader (object instance, XamlSchemaContext schemaContext, XamlObjectReaderSettings settings)
125 if (schemaContext == null)
126 throw new ArgumentNullException ("schemaContext");
127 // FIXME: special case? or can it be generalized?
128 if (instance is Type)
129 instance = new TypeExtension ((Type) instance);
131 this.instance = instance;
132 sctx = schemaContext;
133 this.settings = settings;
135 prefix_lookup = new PrefixLookup (this);
137 if (instance != null) {
138 // check type validity. Note that some checks are done at Read() phase.
139 var type = instance.GetType ();
140 if (!type.IsPublic)
141 throw new XamlObjectReaderException (String.Format ("instance type '{0}' must be public and non-nested.", type));
142 root_type = SchemaContext.GetXamlType (instance.GetType ());
143 if (root_type.ConstructionRequiresArguments && root_type.TypeConverter == null)
144 throw new XamlObjectReaderException (String.Format ("instance type '{0}' has no default constructor.", type));
146 else
147 root_type = XamlLanguage.Null;
150 object instance;
151 XamlType root_type;
152 XamlSchemaContext sctx;
153 XamlObjectReaderSettings settings;
155 INamespacePrefixLookup prefix_lookup;
157 Stack<XamlType> types = new Stack<XamlType> ();
158 Stack<object> objects = new Stack<object> ();
159 Stack<IEnumerator<XamlMember>> members_stack = new Stack<IEnumerator<XamlMember>> ();
160 NSList namespaces;
161 IEnumerator<NamespaceDeclaration> ns_iterator;
162 XamlNodeType node_type = XamlNodeType.None;
163 bool is_eof;
165 public virtual object Instance {
166 get { return NodeType == XamlNodeType.StartObject && objects.Count > 0 ? objects.Peek () : null; }
169 public override bool IsEof {
170 get { return is_eof; }
173 public override XamlMember Member {
174 get { return NodeType == XamlNodeType.StartMember ? members_stack.Peek ().Current : null; }
177 public override NamespaceDeclaration Namespace {
178 get { return NodeType == XamlNodeType.NamespaceDeclaration ? ns_iterator.Current : null; }
181 public override XamlNodeType NodeType {
182 get { return node_type; }
185 public override XamlSchemaContext SchemaContext {
186 get { return sctx; }
189 public override XamlType Type {
190 get { return NodeType == XamlNodeType.StartObject ? types.Peek () : null; }
193 public override object Value {
194 get { return NodeType == XamlNodeType.Value ? objects.Peek () : null; }
197 internal string LookupPrefix (string ns)
199 foreach (var nsd in namespaces)
200 if (nsd.Namespace == ns)
201 return nsd.Prefix;
202 return null;
205 public override bool Read ()
207 if (IsDisposed)
208 throw new ObjectDisposedException ("reader");
209 if (IsEof)
210 return false;
211 IEnumerator<XamlMember> members;
212 switch (NodeType) {
213 case XamlNodeType.None:
214 default:
215 // -> namespaces
216 var d = new Dictionary<string,string> ();
217 //l.Sort ((p1, p2) => String.CompareOrdinal (p1.Key, p2.Key));
218 CollectNamespaces (d, instance, root_type);
219 var nss = from k in d.Keys select new NamespaceDeclaration (k, d [k]);
220 namespaces = new NSList (XamlNodeType.StartObject, nss);
221 namespaces.Sort ((n1, n2) => String.CompareOrdinal (n1.Prefix, n2.Prefix));
222 ns_iterator = namespaces.GetEnumerator ();
224 ns_iterator.MoveNext ();
225 node_type = XamlNodeType.NamespaceDeclaration;
226 return true;
228 case XamlNodeType.NamespaceDeclaration:
229 if (ns_iterator.MoveNext ())
230 return true;
231 node_type = ((NSEnumerator) ns_iterator).OwnerType; // StartObject or StartMember
232 if (node_type == XamlNodeType.StartObject)
233 StartNextObject ();
234 else
235 StartNextMemberOrNamespace ();
236 return true;
238 case XamlNodeType.StartObject:
239 var xt = types.Peek ();
240 members = xt.GetAllReadWriteMembers ().GetEnumerator ();
241 if (members.MoveNext ()) {
242 members_stack.Push (members);
243 StartNextMemberOrNamespace ();
244 return true;
246 else
247 node_type = XamlNodeType.EndObject;
248 return true;
250 case XamlNodeType.StartMember:
251 if (!members_stack.Peek ().Current.IsContentValue ())
252 StartNextObject ();
253 else {
254 var obj = GetMemberValueOrRootInstance ();
255 objects.Push (obj);
256 node_type = XamlNodeType.Value;
258 return true;
260 case XamlNodeType.Value:
261 objects.Pop ();
262 node_type = XamlNodeType.EndMember;
263 return true;
265 case XamlNodeType.GetObject:
266 // how do we get here?
267 throw new NotImplementedException ();
269 case XamlNodeType.EndMember:
270 members = members_stack.Peek ();
271 if (members.MoveNext ()) {
272 members_stack.Push (members);
273 StartNextMemberOrNamespace ();
274 } else {
275 members_stack.Pop ();
276 node_type = XamlNodeType.EndObject;
278 return true;
280 case XamlNodeType.EndObject:
281 // It might be either end of the entire object tree or just the end of an object value.
282 types.Pop ();
283 objects.Pop ();
284 if (objects.Count == 0) {
285 node_type = XamlNodeType.None;
286 is_eof = true;
287 return false;
289 members = members_stack.Peek ();
290 if (members.MoveNext ()) {
291 StartNextMemberOrNamespace ();
292 return true;
294 // then, move to the end of current object member.
295 node_type = XamlNodeType.EndMember;
296 return true;
300 void CollectNamespaces (Dictionary<string,string> d, object o, XamlType xt)
302 if (xt == null)
303 return;
304 if (o == null) {
305 // it becomes NullExtension, so check standard ns.
306 CheckAddNamespace (d, XamlLanguage.Xaml2006Namespace);
307 return;
309 var ns = xt.PreferredXamlNamespace;
310 CheckAddNamespace (d, ns);
312 foreach (var xm in xt.GetAllMembers ()) {
313 ns = xm.PreferredXamlNamespace;
314 if (xm is XamlDirective && ns == XamlLanguage.Xaml2006Namespace)
315 continue;
316 if (xm.Type.IsCollection || xm.Type.IsDictionary || xm.Type.IsArray)
317 continue; // FIXME: process them too.
318 var mv = GetMemberValueOf (xm, o, xt, d);
319 CollectNamespaces (d, mv, xm.Type);
323 // This assumes that the next member is already on current position on current iterator.
324 void StartNextMemberOrNamespace ()
326 // FIXME: there might be NamespaceDeclarations.
327 node_type = XamlNodeType.StartMember;
330 void StartNextObject ()
332 var obj = GetMemberValueOrRootInstance ();
333 var xt = Object.ReferenceEquals (obj, instance) ? root_type : obj != null ? SchemaContext.GetXamlType (obj.GetType ()) : XamlLanguage.Null;
335 // FIXME: enable these lines.
336 // FIXME: if there is an applicable instance descriptor, then it could be still valid.
337 //var type = xt.UnderlyingType;
338 //if (type.GetConstructor (System.Type.EmptyTypes) == null)
339 // throw new XamlObjectReaderException (String.Format ("Type {0} has no default constructor or an instance descriptor.", type));
341 objects.Push (obj);
342 types.Push (xt);
343 node_type = XamlNodeType.StartObject;
346 object GetMemberValueOrRootInstance ()
348 if (objects.Count == 0)
349 return instance;
351 var xm = members_stack.Peek ().Current;
352 var obj = objects.Peek ();
353 var xt = types.Peek ();
354 return GetMemberValueOf (xm, obj, xt, null);
357 object GetMemberValueOf (XamlMember xm, object obj, XamlType xt, Dictionary<string,string> collectingNamespaces)
359 object retobj;
360 XamlType retxt;
361 if (xt.IsContentValue ()) {
362 retxt = xt;
363 retobj = obj;
364 } else {
365 retxt = xm.Type;
366 retobj = xm.GetMemberValueForObjectReader (xt, obj, prefix_lookup);
369 if (collectingNamespaces != null) {
370 if (retobj is Type || retobj is TypeExtension) {
371 var type = (retobj as Type) ?? ((TypeExtension) retobj).Type;
372 if (type == null) // only TypeExtension.TypeName
373 return null;
374 var xtt = SchemaContext.GetXamlType (type);
375 var ns = xtt.PreferredXamlNamespace;
376 var nss = collectingNamespaces;
377 CheckAddNamespace (collectingNamespaces, ns);
378 return null;
380 else if (retxt.IsContentValue ())
381 return null;
382 else
383 return retobj;
384 } else if (retxt.IsContentValue ()) {
385 // FIXME: I'm not sure if this should be really done
386 // here, but every primitive values seem to be exposed
387 // as a string, not a typed object in XamlObjectReader.
388 return retxt.GetStringValue (retobj, prefix_lookup);
390 else
391 return retobj;
394 void CheckAddNamespace (Dictionary<string,string> d, string ns)
396 if (ns == XamlLanguage.Xaml2006Namespace)
397 d [XamlLanguage.Xaml2006Namespace] = "x";
398 else if (!d.ContainsValue (String.Empty))
399 d [ns] = String.Empty;
400 else if (!d.ContainsKey (ns))
401 d.Add (ns, SchemaContext.GetPreferredPrefix (ns));