2 // Copyright (C) 2010 Novell Inc. http://novell.com
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:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
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.
24 using System.Collections;
25 using System.Collections.Generic;
26 using System.Globalization;
28 using System.Reflection;
29 using System.Windows.Markup;
31 using System.Xaml.Schema;
33 using System.Xml.Serialization;
35 // To use this under .NET, compile sources as:
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
43 * StartObject or GetObject
44 These start a new object instance, either by creating new or getting
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.
51 Almost the same as Value. Though the it is likely already instantiated.
53 Indicates a new property as current.
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.
69 public class XamlObjectWriter : XamlWriter, IXamlLineInfoConsumer
71 public XamlObjectWriter (XamlSchemaContext schemaContext)
72 : this (schemaContext, null)
76 public XamlObjectWriter (XamlSchemaContext schemaContext, XamlObjectWriterSettings settings)
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);
86 XamlSchemaContext sctx;
87 XamlObjectWriterSettings settings;
89 XamlObjectWriterInternal intl;
92 bool lineinfo_was_given;
94 public virtual object Result {
95 get { return intl.Result; }
98 public INameScope RootNameScope {
99 get { throw new NotImplementedException (); }
102 public override XamlSchemaContext SchemaContext {
106 public bool ShouldProvideLineInfo {
107 get { return lineinfo_was_given; }
110 public void SetLineInfo (int lineNumber, int linePosition)
113 column = linePosition;
114 lineinfo_was_given = true;
119 throw new NotImplementedException ();
122 protected override void Dispose (bool disposing)
130 protected internal virtual void OnAfterBeginInit (object value)
132 if (settings.AfterBeginInitHandler != null)
133 settings.AfterBeginInitHandler (this, new XamlObjectEventArgs (value));
136 protected internal virtual void OnAfterEndInit (object value)
138 if (settings.AfterEndInitHandler != null)
139 settings.AfterEndInitHandler (this, new XamlObjectEventArgs (value));
142 protected internal virtual void OnAfterProperties (object value)
144 if (settings.AfterPropertiesHandler != null)
145 settings.AfterPropertiesHandler (this, new XamlObjectEventArgs (value));
148 protected internal virtual void OnBeforeProperties (object value)
150 if (settings.BeforePropertiesHandler != null)
151 settings.BeforePropertiesHandler (this, new XamlObjectEventArgs (value));
154 protected internal virtual bool OnSetValue (object eventSender, XamlMember member, object value)
156 if (settings.XamlSetValueHandler != null) {
157 settings.XamlSetValueHandler (eventSender, new XamlSetValueEventArgs (member, value));
163 public override void WriteGetObject ()
165 intl.WriteGetObject ();
168 public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
170 intl.WriteNamespace (namespaceDeclaration);
173 public override void WriteStartObject (XamlType xamlType)
175 intl.WriteStartObject (xamlType);
178 public override void WriteValue (object value)
180 intl.WriteValue (value);
183 public override void WriteStartMember (XamlMember property)
185 intl.WriteStartMember (property);
188 public override void WriteEndObject ()
190 intl.WriteEndObject ();
193 public override void WriteEndMember ()
195 intl.WriteEndMember ();
199 // specific implementation
200 class XamlObjectWriterInternal : XamlWriterInternalBase
202 const string Xmlns2000Namespace = "http://www.w3.org/2000/xmlns/";
204 public XamlObjectWriterInternal (XamlObjectWriter source, XamlSchemaContext schemaContext, XamlWriterStateManager manager)
205 : base (schemaContext, manager)
207 this.source = source;
208 this.sctx = schemaContext;
211 XamlObjectWriter source;
212 XamlSchemaContext sctx;
213 INameScope name_scope = new NameScope ();
214 List<NameFixupRequired> pending_name_references = new List<NameFixupRequired> ();
216 public object Result { get; set; }
218 protected override void OnWriteStartObject ()
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));
226 object_states.Push (state);
227 if (!state.Type.IsContentValue (service_provider))
228 InitializeObjectIfRequired (true);
233 protected override void OnWriteGetObject ()
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);
245 protected override void OnWriteEndObject ()
247 InitializeObjectIfRequired (false); // this is required for such case that there was no StartMember call.
249 var state = object_states.Pop ();
250 var obj = state.Value;
252 if (obj is MarkupExtension) {
254 obj = ((MarkupExtension) obj).ProvideValue (service_provider);
255 } catch (XamlObjectWriterException) {
257 } catch (Exception ex) {
258 throw new XamlObjectWriterException ("An error occured on getting provided value", ex);
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);
271 StoreAppropriatelyTypedValue (obj, state.KeyValue);
272 object_states.Push (state);
273 if (object_states.Count == 1) {
275 ResolvePendingReferences ();
279 Stack<object> escaped_objects = new Stack<object> ();
281 protected override void OnWriteStartMember (XamlMember property)
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> ();
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);
295 static readonly BindingFlags static_flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
297 protected override void OnWriteEndMember ()
299 var xm = CurrentMember;
300 var state = object_states.Peek ();
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);
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);
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 ());
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);
326 if (!xm.IsReadOnly) // exclude read-only object such as collection item.
327 SetValue (xm, CurrentMemberState.Value);
331 void SetValue (XamlMember member, object value)
333 if (member == XamlLanguage.FactoryMethod)
334 object_states.Peek ().FactoryMethod = (string) value;
335 else if (member.IsDirective)
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);
343 void PopulateObject (bool considerPositionalParameters, IList<object> contents)
345 var state = object_states.Peek ();
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;
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;
357 protected override void OnWriteValue (object value)
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);
364 protected override void OnWriteNamespace (NamespaceDeclaration nd)
366 // nothing to do here.
369 void StoreAppropriatelyTypedValue (object obj, object keyObj)
371 var ms = CurrentMemberState; // note that this retrieves parent's current property for EndObject.
373 var state = object_states.Peek ();
374 var parent = state.Value;
377 if (xm == XamlLanguage.Initialization) {
378 state.Value = GetCorrectlyTypedValue (xt, obj);
379 state.IsInstantiated = true;
381 else if (xm.Type.IsXData) {
382 var xdata = (XData) obj;
383 var ixser = xm.Invoker.GetValue (state.Value) as IXmlSerializable;
385 ixser.ReadXml ((XmlReader) xdata.XmlReader);
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);
394 if (!AddToCollectionIfAppropriate (xt, xm, parent, obj, keyObj)) {
396 ms.Value = GetCorrectlyTypedValue (xm.Type, obj);
402 bool AddToCollectionIfAppropriate (XamlType xt, XamlMember xm, object parent, object obj, object keyObj)
405 if (xm == XamlLanguage.Items ||
406 xm == XamlLanguage.PositionalParameters ||
407 xm == XamlLanguage.Arguments) {
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));
418 object GetCorrectlyTypedValue (XamlType xt, object value)
421 return DoGetCorrectlyTypedValue (xt, value);
422 } catch (XamlObjectWriterException) {
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);
430 // It expects that it is not invoked when there is no value to
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)
437 if (xt.IsContentValue (service_provider)) // it is for collection/dictionary key and item
440 return xt.Invoker.CreateInstance (new object [0]);
445 // Not sure if this is really required though...
446 var vt = sctx.GetXamlType (value.GetType ());
447 if (vt.CanAssignTo (xt))
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));
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);
462 if (IsAllowedType (xt, value))
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);
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)"));
475 bool IsAllowedType (XamlType xt, object value)
478 xt.UnderlyingType == null ||
479 xt.UnderlyingType.IsInstanceOfType (value) ||
480 value == null && xt == XamlLanguage.Null ||
481 xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
484 void InitializeObjectIfRequired (bool waitForParameters)
486 var state = object_states.Peek ();
487 if (state.IsInstantiated)
490 if (waitForParameters && (state.Type.ConstructionRequiresArguments || state.Type.HasPositionalParameters (service_provider)))
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
496 if (state.FactoryMethod != null) // FIXME: it must be implemented and verified with tests.
497 throw new NotImplementedException ();
499 obj = state.Type.Invoker.CreateInstance (null);
501 state.IsInstantiated = true;
504 internal IXamlNameResolver name_resolver {
505 get { return (IXamlNameResolver) service_provider.GetService (typeof (IXamlNameResolver)); }
508 void ResolvePendingReferences ()
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);
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);