Merge branch 'bugfix-main-thread-root'
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlObjectWriter.cs
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.
22 //
23 using System;
24 using System.Collections;
25 using System.Collections.Generic;
26 using System.Globalization;
27 using System.Linq;
28 using System.Reflection;
29 using System.Windows.Markup;
30 using System.Xaml;
31 using System.Xaml.Schema;
32 using System.Xml;
33 using System.Xml.Serialization;
34
35 // To use this under .NET, compile sources as:
36 //
37 //      dmcs -d:DOTNET -r:System.Xaml -debug System.Xaml/XamlObjectWriter.cs System.Xaml/XamlWriterInternalBase.cs System.Xaml/TypeExtensionMethods.cs System.Xaml/XamlWriterStateManager.cs System.Xaml/XamlNameResolver.cs System.Xaml/PrefixLookup.cs System.Xaml/ValueSerializerContext.cs ../../build/common/MonoTODOAttribute.cs Test/System.Xaml/TestedTypes.cs
38
39 /*
40
41 State transition:
42
43 * StartObject or GetObject
44         These start a new object instance, either by creating new or getting
45         from parent.
46 * Value
47         This either becomes an entire property value, or an item of current
48         collection, or a key or a value item of current dictionary, or an
49         entire object if it is either Initialization.
50 * EndObject
51         Almost the same as Value. Though the it is likely already instantiated.
52 * StartMember
53         Indicates a new property as current.
54 * EndMember
55         It accompanies a property value (might be lacking), or ends a
56         collection (including those for PositionalParameters), or ends a key
57         property of a dictionary element (if it is Key), or ends an entire
58         value of current object if it is Initialization.
59
60
61 */
62
63 #if DOTNET
64 namespace Mono.Xaml
65 #else
66 namespace System.Xaml
67 #endif
68 {
69         public class XamlObjectWriter : XamlWriter, IXamlLineInfoConsumer
70         {
71                 public XamlObjectWriter (XamlSchemaContext schemaContext)
72                         : this (schemaContext, null)
73                 {
74                 }
75
76                 public XamlObjectWriter (XamlSchemaContext schemaContext, XamlObjectWriterSettings settings)
77                 {
78                         if (schemaContext == null)
79                                 throw new ArgumentNullException ("schemaContext");
80                         this.sctx = schemaContext;
81                         this.settings = settings ?? new XamlObjectWriterSettings ();
82                         var manager = new XamlWriterStateManager<XamlObjectWriterException, XamlObjectWriterException> (false);
83                         intl = new XamlObjectWriterInternal (this, sctx, manager);
84                 }
85
86                 XamlSchemaContext sctx;
87                 XamlObjectWriterSettings settings;
88
89                 XamlObjectWriterInternal intl;
90
91                 int line, column;
92                 bool lineinfo_was_given;
93
94                 public virtual object Result {
95                         get { return intl.Result; }
96                 }
97
98                 public INameScope RootNameScope {
99                         get { throw new NotImplementedException (); }
100                 }
101
102                 public override XamlSchemaContext SchemaContext {
103                         get { return sctx; }
104                 }
105
106                 public bool ShouldProvideLineInfo {
107                         get { return lineinfo_was_given; }
108                 }
109
110                 public void SetLineInfo (int lineNumber, int linePosition)
111                 {
112                         line = lineNumber;
113                         column = linePosition;
114                         lineinfo_was_given = true;
115                 }
116                 
117                 public void Clear ()
118                 {
119                         throw new NotImplementedException ();
120                 }
121
122                 protected override void Dispose (bool disposing)
123                 {
124                         if (!disposing)
125                                 return;
126
127                         intl.CloseAll ();
128                 }
129
130                 protected internal virtual void OnAfterBeginInit (object value)
131                 {
132                         if (settings.AfterBeginInitHandler != null)
133                                 settings.AfterBeginInitHandler (this, new XamlObjectEventArgs (value));
134                 }
135
136                 protected internal virtual void OnAfterEndInit (object value)
137                 {
138                         if (settings.AfterEndInitHandler != null)
139                                 settings.AfterEndInitHandler (this, new XamlObjectEventArgs (value));
140                 }
141
142                 protected internal virtual void OnAfterProperties (object value)
143                 {
144                         if (settings.AfterPropertiesHandler != null)
145                                 settings.AfterPropertiesHandler (this, new XamlObjectEventArgs (value));
146                 }
147
148                 protected internal virtual void OnBeforeProperties (object value)
149                 {
150                         if (settings.BeforePropertiesHandler != null)
151                                 settings.BeforePropertiesHandler (this, new XamlObjectEventArgs (value));
152                 }
153
154                 protected internal virtual bool OnSetValue (object eventSender, XamlMember member, object value)
155                 {
156                         if (settings.XamlSetValueHandler != null) {
157                                 settings.XamlSetValueHandler (eventSender, new XamlSetValueEventArgs (member, value));
158                                 return true;
159                         }
160                         return false;
161                 }
162
163                 public override void WriteGetObject ()
164                 {
165                         intl.WriteGetObject ();
166                 }
167
168                 public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
169                 {
170                         intl.WriteNamespace (namespaceDeclaration);
171                 }
172
173                 public override void WriteStartObject (XamlType xamlType)
174                 {
175                         intl.WriteStartObject (xamlType);
176                 }
177                 
178                 public override void WriteValue (object value)
179                 {
180                         intl.WriteValue (value);
181                 }
182                 
183                 public override void WriteStartMember (XamlMember property)
184                 {
185                         intl.WriteStartMember (property);
186                 }
187                 
188                 public override void WriteEndObject ()
189                 {
190                         intl.WriteEndObject ();
191                 }
192
193                 public override void WriteEndMember ()
194                 {
195                         intl.WriteEndMember ();
196                 }
197         }
198
199         // specific implementation
200         class XamlObjectWriterInternal : XamlWriterInternalBase
201         {
202                 const string Xmlns2000Namespace = "http://www.w3.org/2000/xmlns/";
203
204                 public XamlObjectWriterInternal (XamlObjectWriter source, XamlSchemaContext schemaContext, XamlWriterStateManager manager)
205                         : base (schemaContext, manager)
206                 {
207                         this.source = source;
208                         this.sctx = schemaContext;
209                 }
210                 
211                 XamlObjectWriter source;
212                 XamlSchemaContext sctx;
213                 INameScope name_scope = new NameScope ();
214                 List<NameFixupRequired> pending_name_references = new List<NameFixupRequired> ();
215
216                 public object Result { get; set; }
217                 
218                 protected override void OnWriteStartObject ()
219                 {
220                         var state = object_states.Pop ();
221                         if (object_states.Count > 0) {
222                                 var pstate = object_states.Peek ();
223                                 if (CurrentMemberState.Value != null)
224                                         throw new XamlDuplicateMemberException (String.Format ("Member '{0}' is already written to current type '{1}'", CurrentMember, pstate.Type));
225                         }
226                         object_states.Push (state);
227                         if (!state.Type.IsContentValue (service_provider))
228                                 InitializeObjectIfRequired (true);
229
230
231                 }
232
233                 protected override void OnWriteGetObject ()
234                 {
235                         var state = object_states.Pop ();
236                         var xm = CurrentMember;
237                         var instance = xm.Invoker.GetValue (object_states.Peek ().Value);
238                         if (instance == null)
239                                 throw new XamlObjectWriterException (String.Format ("The value  for '{0}' property is null", xm.Name));
240                         state.Value = instance;
241                         state.IsInstantiated = true;
242                         object_states.Push (state);
243                 }
244
245                 protected override void OnWriteEndObject ()
246                 {
247                         InitializeObjectIfRequired (false); // this is required for such case that there was no StartMember call.
248
249                         var state = object_states.Pop ();
250                         var obj = state.Value;
251                         
252                         if (obj is MarkupExtension) {
253                                 try {
254                                         obj = ((MarkupExtension) obj).ProvideValue (service_provider);
255                                 } catch (XamlObjectWriterException) {
256                                         throw;
257                                 } catch (Exception ex) {
258                                         throw new XamlObjectWriterException ("An error occured on getting provided value", ex);
259                                 }
260                         }
261                         var nfr = obj as NameFixupRequired;
262                         if (nfr != null && object_states.Count > 0) { // IF the root object to be written is x:Reference, then the Result property will become the NameFixupRequired. That's what .NET also does.
263                                 // actually .NET seems to seek "parent" object in its own IXamlNameResolver implementation.
264                                 var pstate = object_states.Peek ();
265                                 nfr.ParentType = pstate.Type;
266                                 nfr.ParentMember = CurrentMember; // Note that it is a member of the pstate.
267                                 nfr.ParentValue = pstate.Value;
268                                 pending_name_references.Add ((NameFixupRequired) obj);
269                         }
270                         else
271                                 StoreAppropriatelyTypedValue (obj, state.KeyValue);
272                         object_states.Push (state);
273                         if (object_states.Count == 1) {
274                                 Result = obj;
275                                 ResolvePendingReferences ();
276                         }
277                 }
278
279                 Stack<object> escaped_objects = new Stack<object> ();
280
281                 protected override void OnWriteStartMember (XamlMember property)
282                 {
283                         if (property == XamlLanguage.PositionalParameters ||
284                             property == XamlLanguage.Arguments) {
285                                 var state = object_states.Peek ();
286                                 escaped_objects.Push (state.Value);
287                                 state.Value = new List<object> ();
288                         }
289
290                         // FIXME: this condition needs to be examined. What is known to be prevented are: PositionalParameters, Initialization and Base (the last one sort of indicates there's a lot more).
291                         else if (!(property is XamlDirective))
292                                 InitializeObjectIfRequired (false);
293                 }
294
295                 static readonly BindingFlags static_flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
296
297                 protected override void OnWriteEndMember ()
298                 {
299                         var xm = CurrentMember;
300                         var state = object_states.Peek ();
301
302                         if (xm == XamlLanguage.PositionalParameters) {
303                                 var l = (List<object>) state.Value;
304                                 state.Value = escaped_objects.Pop ();
305                                 state.IsInstantiated = true;
306                                 PopulateObject (true, l);
307                                 return;
308                         } else if (xm == XamlLanguage.Arguments) {
309                                 if (state.FactoryMethod != null) {
310                                         var contents = (List<object>) state.Value;
311                                         var mi = state.Type.UnderlyingType.GetMethods (static_flags).FirstOrDefault (mii => mii.Name == state.FactoryMethod && mii.GetParameters ().Length == contents.Count);
312                                         if (mi == null)
313                                                 throw new XamlObjectWriterException (String.Format ("Specified static factory method '{0}' for type '{1}' was not found", state.FactoryMethod, state.Type));
314                                         state.Value = mi.Invoke (null, contents.ToArray ());
315                                 }
316                                 else
317                                         PopulateObject (false, (List<object>) state.Value);
318                                 state.IsInstantiated = true;
319                                 escaped_objects.Pop ();
320                         } else if (xm == XamlLanguage.Initialization) {
321                                 // ... and no need to do anything. The object value to pop *is* the return value.
322                         } else if (xm == XamlLanguage.Name || xm == state.Type.GetAliasedProperty (XamlLanguage.Name)) {
323                                 string name = (string) CurrentMemberState.Value;
324                                 name_scope.RegisterName (name, state.Value);
325                         } else {
326                                 if (xm.IsEvent)
327                                         SetEvent (xm, (string) CurrentMemberState.Value);
328                                 else if (!xm.IsReadOnly) // exclude read-only object such as collection item.
329                                         SetValue (xm, CurrentMemberState.Value);
330                         }
331                 }
332
333                 void SetEvent (XamlMember member, string value)
334                 {
335                         if (member.UnderlyingMember == null)
336                                 throw new XamlObjectWriterException (String.Format ("Event {0} has no underlying member to attach event", member));
337
338                         int idx = value.LastIndexOf ('.');
339                         var xt = idx < 0 ? member.DeclaringType : ResolveTypeFromName (value.Substring (0, idx));
340                         if (xt == null)
341                                 throw new XamlObjectWriterException (String.Format ("Referenced type {0} in event {1} was not found", value, member));
342                         if (xt.UnderlyingType == null)
343                                 throw new XamlObjectWriterException (String.Format ("Referenced type {0} in event {1} has no underlying type", value, member));
344                         string mn = idx < 0 ? value : value.Substring (idx + 1);
345                         var ev = (EventInfo) member.UnderlyingMember;
346                         // get an appropriate MethodInfo overload whose signature matches the event's handler type.
347                         // FIXME: this may need more strict match. RuntimeBinder may be useful here.
348                         var eventMethodParams = ev.EventHandlerType.GetMethod ("Invoke").GetParameters ();
349                         var mi = xt.UnderlyingType.GetMethod (mn, (from pi in eventMethodParams select pi.ParameterType).ToArray ());
350                         if (mi == null)
351                                 throw new XamlObjectWriterException (String.Format ("Referenced value method {0} in type {1} indicated by event {2} was not found", mn, value, member));
352                         var obj = object_states.Peek ().Value;
353                         ev.AddEventHandler (obj, Delegate.CreateDelegate (ev.EventHandlerType, obj, mi));
354                 }
355
356                 void SetValue (XamlMember member, object value)
357                 {
358                         if (member == XamlLanguage.FactoryMethod)
359                                 object_states.Peek ().FactoryMethod = (string) value;
360                         else if (member.IsDirective)
361                                 return;
362                         else if (member.IsAttachable)
363                                 AttachablePropertyServices.SetProperty (object_states.Peek ().Value, new AttachableMemberIdentifier (member.DeclaringType.UnderlyingType, member.Name), value);
364                         else if (!source.OnSetValue (this, member, value))
365                                 member.Invoker.SetValue (object_states.Peek ().Value, value);
366                 }
367
368                 void PopulateObject (bool considerPositionalParameters, IList<object> contents)
369                 {
370                         var state = object_states.Peek ();
371
372                         var args = state.Type.GetSortedConstructorArguments ();
373                         var argt = args != null ? (IList<XamlType>) (from arg in args select arg.Type).ToArray () : considerPositionalParameters ? state.Type.GetPositionalParameters (contents.Count) : null;
374
375                         var argv = new object [argt.Count];
376                         for (int i = 0; i < argv.Length; i++)
377                                 argv [i] = GetCorrectlyTypedValue (argt [i], contents [i]);
378                         state.Value = state.Type.Invoker.CreateInstance (argv);
379                         state.IsInstantiated = true;
380                 }
381
382                 protected override void OnWriteValue (object value)
383                 {
384                         if (CurrentMemberState.Value != null)
385                                 throw new XamlDuplicateMemberException (String.Format ("Member '{0}' is already written to current type '{1}'", CurrentMember, object_states.Peek ().Type));
386                         StoreAppropriatelyTypedValue (value, null);
387                 }
388
389                 protected override void OnWriteNamespace (NamespaceDeclaration nd)
390                 {
391                         // nothing to do here.
392                 }
393                 
394                 void StoreAppropriatelyTypedValue (object obj, object keyObj)
395                 {
396                         var ms = CurrentMemberState; // note that this retrieves parent's current property for EndObject.
397                         if (ms != null) {
398                                 var state = object_states.Peek ();
399                                 var parent = state.Value;
400                                 var xt = state.Type;
401                                 var xm = ms.Member;
402                                 if (xm == XamlLanguage.Initialization) {
403                                         state.Value = GetCorrectlyTypedValue (xt, obj);
404                                         state.IsInstantiated = true;
405                                 } else if (xm.IsEvent) {
406                                         ms.Value = (string) obj; // save name of value delegate (method).
407                                         state.IsInstantiated = true;
408                                 } else if (xm.Type.IsXData) {
409                                         var xdata = (XData) obj;
410                                         var ixser = xm.Invoker.GetValue (state.Value) as IXmlSerializable;
411                                         if (ixser != null)
412                                                 ixser.ReadXml ((XmlReader) xdata.XmlReader);
413                                 }
414                                 else if (xm == XamlLanguage.Base)
415                                         ms.Value = GetCorrectlyTypedValue (xm.Type, obj);
416                                 else if (xm == XamlLanguage.Name || xm == xt.GetAliasedProperty (XamlLanguage.Name))
417                                         ms.Value = GetCorrectlyTypedValue (XamlLanguage.String, obj);
418                                 else if (xm == XamlLanguage.Key)
419                                         state.KeyValue = GetCorrectlyTypedValue (xt.KeyType, obj);
420                                 else {
421                                         if (!AddToCollectionIfAppropriate (xt, xm, parent, obj, keyObj)) {
422                                                 if (!xm.IsReadOnly)
423                                                         ms.Value = GetCorrectlyTypedValue (xm.Type, obj);
424                                         }
425                                 }
426                         }
427                 }
428
429                 bool AddToCollectionIfAppropriate (XamlType xt, XamlMember xm, object parent, object obj, object keyObj)
430                 {
431                         var mt = xm.Type;
432                         if (xm == XamlLanguage.Items ||
433                             xm == XamlLanguage.PositionalParameters ||
434                             xm == XamlLanguage.Arguments) {
435                                 if (xt.IsDictionary)
436                                         mt.Invoker.AddToDictionary (parent, GetCorrectlyTypedValue (xt.KeyType, keyObj), GetCorrectlyTypedValue (xt.ItemType, obj));
437                                 else // collection. Note that state.Type isn't usable for PositionalParameters to identify collection kind.
438                                         mt.Invoker.AddToCollection (parent, GetCorrectlyTypedValue (xt.ItemType, obj));
439                                 return true;
440                         }
441                         else
442                                 return false;
443                 }
444
445                 object GetCorrectlyTypedValue (XamlType xt, object value)
446                 {
447                         try {
448                                 return DoGetCorrectlyTypedValue (xt, value);
449                         } catch (XamlObjectWriterException) {
450                                 throw;
451                         } catch (Exception ex) {
452                                 // For + ex.Message, the runtime should print InnerException message like .NET does.
453                                 throw new XamlObjectWriterException (String.Format ("Could not convert object \'{0}' (of type {1}) to {2}: ", value, value != null ? (object) value.GetType () : "(null)", xt)  + ex.Message, ex);
454                         }
455                 }
456
457                 // It expects that it is not invoked when there is no value to 
458                 // assign.
459                 // When it is passed null, then it returns a default instance.
460                 // For example, passing null as Int32 results in 0.
461                 // But do not immediately try to instantiate with the type, since the type might be abstract.
462                 object DoGetCorrectlyTypedValue (XamlType xt, object value)
463                 {
464                         if (value == null) {
465                                 if (xt.IsContentValue (service_provider)) // it is for collection/dictionary key and item
466                                         return null;
467                                 else
468                                         return xt.IsNullable ? null : xt.Invoker.CreateInstance (new object [0]);
469                         }
470                         if (xt == null)
471                                 return value;
472
473                         // Not sure if this is really required though...
474                         var vt = sctx.GetXamlType (value.GetType ());
475                         if (vt.CanAssignTo (xt))
476                                 return value;
477
478                         // FIXME: this could be generalized by some means, but I cannot find any.
479                         if (xt.UnderlyingType == typeof (XamlType) && value is string)
480                                 value = ResolveTypeFromName ((string) value);
481
482                         // FIXME: this could be generalized by some means, but I cannot find any.
483                         if (xt.UnderlyingType == typeof (Type))
484                                 value = new TypeExtension ((string) value).ProvideValue (service_provider);
485                         if (xt == XamlLanguage.Type && value is string)
486                                 value = new TypeExtension ((string) value);
487                         
488                         if (IsAllowedType (xt, value))
489                                 return value;
490
491                         if (xt.TypeConverter != null && value != null) {
492                                 var tc = xt.TypeConverter.ConverterInstance;
493                                 if (tc != null && tc.CanConvertFrom (value.GetType ()))
494                                         value = tc.ConvertFrom (value);
495                                 return value;
496                         }
497
498                         throw new XamlObjectWriterException (String.Format ("Value '{1}' (of type {2}) is not of or convertible to type {0}", xt, value, value != null ? (object) value.GetType () : "(null)"));
499                 }
500
501                 XamlType ResolveTypeFromName (string name)
502                 {
503                         var nsr = (IXamlNamespaceResolver) service_provider.GetService (typeof (IXamlNamespaceResolver));
504                         return sctx.GetXamlType (XamlTypeName.Parse (name, nsr));
505                 }
506
507                 bool IsAllowedType (XamlType xt, object value)
508                 {
509                         return  xt == null ||
510                                 xt.UnderlyingType == null ||
511                                 xt.UnderlyingType.IsInstanceOfType (value) ||
512                                 value == null && xt == XamlLanguage.Null ||
513                                 xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
514                 }
515                 
516                 void InitializeObjectIfRequired (bool waitForParameters)
517                 {
518                         var state = object_states.Peek ();
519                         if (state.IsInstantiated)
520                                 return;
521
522                         if (waitForParameters && (state.Type.ConstructionRequiresArguments || state.Type.HasPositionalParameters (service_provider)))
523                                 return;
524
525                         // FIXME: "The default techniques in absence of a factory method are to attempt to find a default constructor, then attempt to find an identified type converter on type, member, or destination type."
526                         // http://msdn.microsoft.com/en-us/library/system.xaml.xamllanguage.factorymethod%28VS.100%29.aspx
527                         object obj;
528                         if (state.FactoryMethod != null) // FIXME: it must be implemented and verified with tests.
529                                 throw new NotImplementedException ();
530                         else
531                                 obj = state.Type.Invoker.CreateInstance (null);
532                         state.Value = obj;
533                         state.IsInstantiated = true;
534                 }
535
536                 internal IXamlNameResolver name_resolver {
537                         get { return (IXamlNameResolver) service_provider.GetService (typeof (IXamlNameResolver)); }
538                 }
539
540                 void ResolvePendingReferences ()
541                 {
542                         foreach (var fixup in pending_name_references) {
543                                 foreach (var name in fixup.Names) {
544                                         bool isFullyInitialized;
545                                         // FIXME: sort out relationship between name_scope and name_resolver. (unify to name_resolver, probably)
546                                         var obj = name_scope.FindName (name) ?? name_resolver.Resolve (name, out isFullyInitialized);
547                                         if (obj == null)
548                                                 throw new XamlObjectWriterException (String.Format ("Unresolved object reference '{0}' was found", name));
549                                         if (!AddToCollectionIfAppropriate (fixup.ParentType, fixup.ParentMember, fixup.ParentValue, obj, null)) // FIXME: is keyObj always null?
550                                                 fixup.ParentMember.Invoker.SetValue (fixup.ParentValue, obj);
551                                 }
552                         }
553                 }
554         }
555 }