Merge remote branch 'upstream/master'
[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.IsReadOnly) // exclude read-only object such as collection item.
327                                         SetValue (xm, CurrentMemberState.Value);
328                         }
329                 }
330
331                 void SetValue (XamlMember member, object value)
332                 {
333                         if (member == XamlLanguage.FactoryMethod)
334                                 object_states.Peek ().FactoryMethod = (string) value;
335                         else if (member.IsDirective)
336                                 return;
337                         else if (member.IsAttachable)
338                                 AttachablePropertyServices.SetProperty (object_states.Peek ().Value, new AttachableMemberIdentifier (member.DeclaringType.UnderlyingType, member.Name), value);
339                         else if (!source.OnSetValue (this, member, value))
340                                 member.Invoker.SetValue (object_states.Peek ().Value, value);
341                 }
342
343                 void PopulateObject (bool considerPositionalParameters, IList<object> contents)
344                 {
345                         var state = object_states.Peek ();
346
347                         var args = state.Type.GetSortedConstructorArguments ();
348                         var argt = args != null ? (IList<XamlType>) (from arg in args select arg.Type).ToArray () : considerPositionalParameters ? state.Type.GetPositionalParameters (contents.Count) : null;
349
350                         var argv = new object [argt.Count];
351                         for (int i = 0; i < argv.Length; i++)
352                                 argv [i] = GetCorrectlyTypedValue (argt [i], contents [i]);
353                         state.Value = state.Type.Invoker.CreateInstance (argv);
354                         state.IsInstantiated = true;
355                 }
356
357                 protected override void OnWriteValue (object value)
358                 {
359                         if (CurrentMemberState.Value != null)
360                                 throw new XamlDuplicateMemberException (String.Format ("Member '{0}' is already written to current type '{1}'", CurrentMember, object_states.Peek ().Type));
361                         StoreAppropriatelyTypedValue (value, null);
362                 }
363
364                 protected override void OnWriteNamespace (NamespaceDeclaration nd)
365                 {
366                         // nothing to do here.
367                 }
368                 
369                 void StoreAppropriatelyTypedValue (object obj, object keyObj)
370                 {
371                         var ms = CurrentMemberState; // note that this retrieves parent's current property for EndObject.
372                         if (ms != null) {
373                                 var state = object_states.Peek ();
374                                 var parent = state.Value;
375                                 var xt = state.Type;
376                                 var xm = ms.Member;
377                                 if (xm == XamlLanguage.Initialization) {
378                                         state.Value = GetCorrectlyTypedValue (xt, obj);
379                                         state.IsInstantiated = true;
380                                 }
381                                 else if (xm.Type.IsXData) {
382                                         var xdata = (XData) obj;
383                                         var ixser = xm.Invoker.GetValue (state.Value) as IXmlSerializable;
384                                         if (ixser != null)
385                                                 ixser.ReadXml ((XmlReader) xdata.XmlReader);
386                                 }
387                                 else if (xm == XamlLanguage.Base)
388                                         ms.Value = GetCorrectlyTypedValue (xm.Type, obj);
389                                 else if (xm == XamlLanguage.Name || xm == xt.GetAliasedProperty (XamlLanguage.Name))
390                                         ms.Value = GetCorrectlyTypedValue (XamlLanguage.String, obj);
391                                 else if (xm == XamlLanguage.Key)
392                                         state.KeyValue = GetCorrectlyTypedValue (xt.KeyType, obj);
393                                 else {
394                                         if (!AddToCollectionIfAppropriate (xt, xm, parent, obj, keyObj)) {
395                                                 if (!xm.IsReadOnly)
396                                                         ms.Value = GetCorrectlyTypedValue (xm.Type, obj);
397                                         }
398                                 }
399                         }
400                 }
401
402                 bool AddToCollectionIfAppropriate (XamlType xt, XamlMember xm, object parent, object obj, object keyObj)
403                 {
404                         var mt = xm.Type;
405                         if (xm == XamlLanguage.Items ||
406                             xm == XamlLanguage.PositionalParameters ||
407                             xm == XamlLanguage.Arguments) {
408                                 if (xt.IsDictionary)
409                                         mt.Invoker.AddToDictionary (parent, GetCorrectlyTypedValue (xt.KeyType, keyObj), GetCorrectlyTypedValue (xt.ItemType, obj));
410                                 else // collection. Note that state.Type isn't usable for PositionalParameters to identify collection kind.
411                                         mt.Invoker.AddToCollection (parent, GetCorrectlyTypedValue (xt.ItemType, obj));
412                                 return true;
413                         }
414                         else
415                                 return false;
416                 }
417
418                 object GetCorrectlyTypedValue (XamlType xt, object value)
419                 {
420                         try {
421                                 return DoGetCorrectlyTypedValue (xt, value);
422                         } catch (XamlObjectWriterException) {
423                                 throw;
424                         } catch (Exception ex) {
425                                 // For + ex.Message, the runtime should print InnerException message like .NET does.
426                                 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);
427                         }
428                 }
429
430                 // It expects that it is not invoked when there is no value to 
431                 // assign.
432                 // When it is passed null, then it returns a default instance.
433                 // For example, passing null as Int32 results in 0.
434                 object DoGetCorrectlyTypedValue (XamlType xt, object value)
435                 {
436                         if (value == null) {
437                                 if (xt.IsContentValue (service_provider)) // it is for collection/dictionary key and item
438                                         return null;
439                                 else
440                                         return xt.Invoker.CreateInstance (new object [0]);
441                         }
442                         if (xt == null)
443                                 return value;
444
445                         // Not sure if this is really required though...
446                         var vt = sctx.GetXamlType (value.GetType ());
447                         if (vt.CanAssignTo (xt))
448                                 return value;
449
450                         // FIXME: this could be generalized by some means, but I cannot find any.
451                         if (xt.UnderlyingType == typeof (XamlType) && value is string) {
452                                 var nsr = (IXamlNamespaceResolver) service_provider.GetService (typeof (IXamlNamespaceResolver));
453                                 value = sctx.GetXamlType (XamlTypeName.Parse ((string) value, nsr));
454                         }
455
456                         // FIXME: this could be generalized by some means, but I cannot find any.
457                         if (xt.UnderlyingType == typeof (Type))
458                                 value = new TypeExtension ((string) value).ProvideValue (service_provider);
459                         if (xt == XamlLanguage.Type && value is string)
460                                 value = new TypeExtension ((string) value);
461                         
462                         if (IsAllowedType (xt, value))
463                                 return value;
464
465                         if (xt.TypeConverter != null && value != null) {
466                                 var tc = xt.TypeConverter.ConverterInstance;
467                                 if (tc != null && tc.CanConvertFrom (value.GetType ()))
468                                         value = tc.ConvertFrom (value);
469                                 return value;
470                         }
471
472                         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)"));
473                 }
474
475                 bool IsAllowedType (XamlType xt, object value)
476                 {
477                         return  xt == null ||
478                                 xt.UnderlyingType == null ||
479                                 xt.UnderlyingType.IsInstanceOfType (value) ||
480                                 value == null && xt == XamlLanguage.Null ||
481                                 xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
482                 }
483                 
484                 void InitializeObjectIfRequired (bool waitForParameters)
485                 {
486                         var state = object_states.Peek ();
487                         if (state.IsInstantiated)
488                                 return;
489
490                         if (waitForParameters && (state.Type.ConstructionRequiresArguments || state.Type.HasPositionalParameters (service_provider)))
491                                 return;
492
493                         // 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."
494                         // http://msdn.microsoft.com/en-us/library/system.xaml.xamllanguage.factorymethod%28VS.100%29.aspx
495                         object obj;
496                         if (state.FactoryMethod != null) // FIXME: it must be implemented and verified with tests.
497                                 throw new NotImplementedException ();
498                         else
499                                 obj = state.Type.Invoker.CreateInstance (null);
500                         state.Value = obj;
501                         state.IsInstantiated = true;
502                 }
503
504                 internal IXamlNameResolver name_resolver {
505                         get { return (IXamlNameResolver) service_provider.GetService (typeof (IXamlNameResolver)); }
506                 }
507
508                 void ResolvePendingReferences ()
509                 {
510                         foreach (var fixup in pending_name_references) {
511                                 foreach (var name in fixup.Names) {
512                                         bool isFullyInitialized;
513                                         // FIXME: sort out relationship between name_scope and name_resolver. (unify to name_resolver, probably)
514                                         var obj = name_scope.FindName (name) ?? name_resolver.Resolve (name, out isFullyInitialized);
515                                         if (obj == null)
516                                                 throw new XamlObjectWriterException (String.Format ("Unresolved object reference '{0}' was found", name));
517                                         if (!AddToCollectionIfAppropriate (fixup.ParentType, fixup.ParentMember, fixup.ParentValue, obj, null)) // FIXME: is keyObj always null?
518                                                 fixup.ParentMember.Invoker.SetValue (fixup.ParentValue, obj);
519                                 }
520                         }
521                 }
522         }
523 }