/mcs/class/System.Xaml/System.Xaml/XamlObjectWriter.cs
C# | 626 lines | 475 code | 79 blank | 72 comment | 157 complexity | 65c12a16db7c5a37d5985b90ceede0e4 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
- //
- // Copyright (C) 2010 Novell Inc. http://novell.com
- // Copyright (C) 2012 Xamarin Inc. http://xamarin.com
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Globalization;
- using System.Linq;
- using System.Reflection;
- using System.Windows.Markup;
- using System.Xaml;
- using System.Xaml.Schema;
- using System.Xml;
- using System.Xml.Serialization;
- // To use this under .NET, compile sources as:
- //
- // 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
- /*
- State transition:
- * StartObject or GetObject
- These start a new object instance, either by creating new or getting
- from parent.
- * Value
- This either becomes an entire property value, or an item of current
- collection, or a key or a value item of current dictionary, or an
- entire object if it is either Initialization.
- * EndObject
- Almost the same as Value. Though the it is likely already instantiated.
- * StartMember
- Indicates a new property as current.
- * EndMember
- It accompanies a property value (might be lacking), or ends a
- collection (including those for PositionalParameters), or ends a key
- property of a dictionary element (if it is Key), or ends an entire
- value of current object if it is Initialization.
- */
- #if DOTNET
- namespace Mono.Xaml
- #else
- namespace System.Xaml
- #endif
- {
- public class XamlObjectWriter : XamlWriter, IXamlLineInfoConsumer
- {
- public XamlObjectWriter (XamlSchemaContext schemaContext)
- : this (schemaContext, null)
- {
- }
- public XamlObjectWriter (XamlSchemaContext schemaContext, XamlObjectWriterSettings settings)
- {
- if (schemaContext == null)
- throw new ArgumentNullException ("schemaContext");
- this.sctx = schemaContext;
- this.settings = settings ?? new XamlObjectWriterSettings ();
- var manager = new XamlWriterStateManager<XamlObjectWriterException, XamlObjectWriterException> (false);
- intl = new XamlObjectWriterInternal (this, sctx, manager);
- }
- XamlSchemaContext sctx;
- XamlObjectWriterSettings settings;
- XamlObjectWriterInternal intl;
- //int line, column;
- bool lineinfo_was_given;
- internal XamlObjectWriterSettings Settings {
- get { return settings; }
- }
- public virtual object Result {
- get { return intl.Result; }
- }
- public INameScope RootNameScope {
- get { return intl.NameScope; }
- }
- public override XamlSchemaContext SchemaContext {
- get { return sctx; }
- }
- public bool ShouldProvideLineInfo {
- get { return lineinfo_was_given; }
- }
- public void SetLineInfo (int lineNumber, int linePosition)
- {
- // line = lineNumber;
- // column = linePosition;
- lineinfo_was_given = true;
- }
-
- public void Clear ()
- {
- throw new NotImplementedException ();
- }
- protected override void Dispose (bool disposing)
- {
- if (!disposing)
- return;
- intl.CloseAll ();
- }
- protected internal virtual void OnAfterBeginInit (object value)
- {
- if (settings.AfterBeginInitHandler != null)
- settings.AfterBeginInitHandler (this, new XamlObjectEventArgs (value));
- }
- protected internal virtual void OnAfterEndInit (object value)
- {
- if (settings.AfterEndInitHandler != null)
- settings.AfterEndInitHandler (this, new XamlObjectEventArgs (value));
- }
- protected internal virtual void OnAfterProperties (object value)
- {
- if (settings.AfterPropertiesHandler != null)
- settings.AfterPropertiesHandler (this, new XamlObjectEventArgs (value));
- }
- protected internal virtual void OnBeforeProperties (object value)
- {
- if (settings.BeforePropertiesHandler != null)
- settings.BeforePropertiesHandler (this, new XamlObjectEventArgs (value));
- }
- protected internal virtual bool OnSetValue (object eventSender, XamlMember member, object value)
- {
- if (settings.XamlSetValueHandler != null) {
- var args = new XamlSetValueEventArgs (member, value);
- settings.XamlSetValueHandler (eventSender, args);
- return args.Handled;
- }
- return false;
- }
- public override void WriteGetObject ()
- {
- intl.WriteGetObject ();
- }
- public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
- {
- intl.WriteNamespace (namespaceDeclaration);
- }
- public override void WriteStartObject (XamlType xamlType)
- {
- intl.WriteStartObject (xamlType);
- }
-
- public override void WriteValue (object value)
- {
- intl.WriteValue (value);
- }
-
- public override void WriteStartMember (XamlMember property)
- {
- intl.WriteStartMember (property);
- }
-
- public override void WriteEndObject ()
- {
- intl.WriteEndObject ();
- }
- public override void WriteEndMember ()
- {
- intl.WriteEndMember ();
- }
- }
- // specific implementation
- class XamlObjectWriterInternal : XamlWriterInternalBase
- {
- const string Xmlns2000Namespace = "http://www.w3.org/2000/xmlns/";
- public XamlObjectWriterInternal (XamlObjectWriter source, XamlSchemaContext schemaContext, XamlWriterStateManager manager)
- : base (schemaContext, manager)
- {
- this.source = source;
- this.sctx = schemaContext;
- var ext = source.Settings.ExternalNameScope;
- name_scope = ext != null && source.Settings.RegisterNamesOnExternalNamescope ? ext : new NameScope (ext);
- }
-
- XamlObjectWriter source;
- XamlSchemaContext sctx;
- INameScope name_scope;
- List<NameFixupRequired> pending_name_references = new List<NameFixupRequired> ();
- AmbientProvider ambient_provider = new AmbientProvider ();
- public INameScope NameScope {
- get { return name_scope; }
- }
- public object Result { get; set; }
-
- protected override void OnWriteStartObject ()
- {
- var state = object_states.Pop ();
- if (object_states.Count > 0) {
- var pstate = object_states.Peek ();
- if (CurrentMemberState.Value != null)
- throw new XamlDuplicateMemberException (String.Format ("Member '{0}' is already written to current type '{1}'", CurrentMember, pstate.Type));
- } else {
- var obj = source.Settings.RootObjectInstance;
- if (obj != null) {
- if (state.Type.UnderlyingType != null && !state.Type.UnderlyingType.IsAssignableFrom (obj.GetType ()))
- throw new XamlObjectWriterException (String.Format ("RootObjectInstance type '{0}' is not assignable to '{1}'", obj.GetType (), state.Type));
- state.Value = obj;
- state.IsInstantiated = true;
- }
- root_state = state;
- }
- object_states.Push (state);
- if (!state.Type.IsContentValue (service_provider))
- InitializeObjectIfRequired (true);
- state.IsXamlWriterCreated = true;
- source.OnBeforeProperties (state.Value);
- }
- protected override void OnWriteGetObject ()
- {
- var state = object_states.Pop ();
- var xm = CurrentMember;
- var instance = xm.Invoker.GetValue (object_states.Peek ().Value);
- if (instance == null)
- throw new XamlObjectWriterException (String.Format ("The value for '{0}' property is null", xm.Name));
- state.Value = instance;
- state.IsInstantiated = true;
- object_states.Push (state);
- }
- protected override void OnWriteEndObject ()
- {
- InitializeObjectIfRequired (false); // this is required for such case that there was no StartMember call.
- var state = object_states.Pop ();
- var obj = state.Value;
-
- if (obj is MarkupExtension) {
- try {
- obj = ((MarkupExtension) obj).ProvideValue (service_provider);
- } catch (XamlObjectWriterException) {
- throw;
- } catch (Exception ex) {
- throw new XamlObjectWriterException ("An error occured on getting provided value", ex);
- }
- }
-
- // call this (possibly) before the object is added to parent collection. (bug #3003 also expects this)
- if (state.IsXamlWriterCreated)
- source.OnAfterProperties (obj);
-
- var nfr = obj as NameFixupRequired;
- 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.
- // actually .NET seems to seek "parent" object in its own IXamlNameResolver implementation.
- var pstate = object_states.Peek ();
- nfr.ParentType = pstate.Type;
- nfr.ParentMember = CurrentMember; // Note that it is a member of the pstate.
- nfr.ParentValue = pstate.Value;
- pending_name_references.Add ((NameFixupRequired) obj);
- }
- else
- StoreAppropriatelyTypedValue (obj, state.KeyValue);
-
- if (state.Type.IsAmbient)
- ambient_provider.Pop ();
- else
- HandleEndInit (obj);
-
- object_states.Push (state);
- if (object_states.Count == 1) {
- Result = obj;
- ResolvePendingReferences ();
- }
- }
- Stack<object> escaped_objects = new Stack<object> ();
- protected override void OnWriteStartMember (XamlMember property)
- {
- if (property == XamlLanguage.PositionalParameters ||
- property == XamlLanguage.Arguments) {
- var state = object_states.Peek ();
- escaped_objects.Push (state.Value);
- state.Value = new List<object> ();
- }
- // 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).
- else if (!(property is XamlDirective))
- InitializeObjectIfRequired (false);
- }
- static readonly BindingFlags static_flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
- protected override void OnWriteEndMember ()
- {
- var xm = CurrentMember;
- var state = object_states.Peek ();
- if (xm == XamlLanguage.PositionalParameters) {
- var l = (List<object>) state.Value;
- state.Value = escaped_objects.Pop ();
- state.IsInstantiated = true;
- PopulateObject (true, l);
- return;
- } else if (xm == XamlLanguage.Arguments) {
- if (state.FactoryMethod != null) {
- var contents = (List<object>) state.Value;
- var mi = state.Type.UnderlyingType.GetMethods (static_flags).FirstOrDefault (mii => mii.Name == state.FactoryMethod && mii.GetParameters ().Length == contents.Count);
- if (mi == null)
- throw new XamlObjectWriterException (String.Format ("Specified static factory method '{0}' for type '{1}' was not found", state.FactoryMethod, state.Type));
- state.Value = mi.Invoke (null, contents.ToArray ());
- }
- else
- PopulateObject (false, (List<object>) state.Value);
- state.IsInstantiated = true;
- escaped_objects.Pop ();
- } else if (xm == XamlLanguage.Initialization) {
- // ... and no need to do anything. The object value to pop *is* the return value.
- } else if (xm == XamlLanguage.Name || xm == state.Type.GetAliasedProperty (XamlLanguage.Name)) {
- string name = (string) CurrentMemberState.Value;
- name_scope.RegisterName (name, state.Value);
- } else {
- if (xm.IsEvent)
- SetEvent (xm, (string) CurrentMemberState.Value);
- else if (!xm.IsReadOnly) // exclude read-only object such as collection item.
- SetValue (xm, CurrentMemberState.Value);
- }
- }
- void SetEvent (XamlMember member, string value)
- {
- if (member.UnderlyingMember == null)
- throw new XamlObjectWriterException (String.Format ("Event {0} has no underlying member to attach event", member));
- int idx = value.LastIndexOf ('.');
- var xt = idx < 0 ? root_state.Type : ResolveTypeFromName (value.Substring (0, idx));
- if (xt == null)
- throw new XamlObjectWriterException (String.Format ("Referenced type {0} in event {1} was not found", value, member));
- if (xt.UnderlyingType == null)
- throw new XamlObjectWriterException (String.Format ("Referenced type {0} in event {1} has no underlying type", value, member));
- string mn = idx < 0 ? value : value.Substring (idx + 1);
- var ev = (EventInfo) member.UnderlyingMember;
- // get an appropriate MethodInfo overload whose signature matches the event's handler type.
- // FIXME: this may need more strict match. RuntimeBinder may be useful here.
- var eventMethodParams = ev.EventHandlerType.GetMethod ("Invoke").GetParameters ();
-
- var target = root_state.Value;
- var mi = target.GetType().GetMethod (mn, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, (from pi in eventMethodParams select pi.ParameterType).ToArray (), null);
- if (mi == null)
- throw new XamlObjectWriterException (String.Format ("Referenced value method {0} in type {1} indicated by event {2} was not found", mn, value, member));
- var obj = object_states.Peek ().Value;
- ev.AddEventHandler (obj, Delegate.CreateDelegate (ev.EventHandlerType, target, mi));
- }
- void SetValue (XamlMember member, object value)
- {
- if (member == XamlLanguage.FactoryMethod)
- object_states.Peek ().FactoryMethod = (string) value;
- else if (member.IsDirective)
- return;
- else
- SetValue (member, object_states.Peek ().Value, value);
- }
-
- void SetValue (XamlMember member, object target, object value)
- {
- if (!source.OnSetValue (target, member, value))
- member.Invoker.SetValue (target, value);
- }
- void PopulateObject (bool considerPositionalParameters, IList<object> contents)
- {
- var state = object_states.Peek ();
- var args = state.Type.GetSortedConstructorArguments ().ToArray ();
- var argt = args != null ? (IList<XamlType>) (from arg in args select arg.Type).ToArray () : considerPositionalParameters ? state.Type.GetPositionalParameters (contents.Count) : null;
- var argv = new object [argt.Count];
- for (int i = 0; i < argv.Length; i++)
- argv [i] = GetCorrectlyTypedValue (args [i], argt [i], contents [i]);
- state.Value = state.Type.Invoker.CreateInstance (argv);
- state.IsInstantiated = true;
- if (state.Type.IsAmbient)
- ambient_provider.Push (new AmbientPropertyValue (CurrentMember, state.Value));
- HandleBeginInit (state.Value);
- }
- protected override void OnWriteValue (object value)
- {
- if (CurrentMemberState.Value != null)
- throw new XamlDuplicateMemberException (String.Format ("Member '{0}' is already written to current type '{1}'", CurrentMember, object_states.Peek ().Type));
- StoreAppropriatelyTypedValue (value, null);
- }
- protected override void OnWriteNamespace (NamespaceDeclaration nd)
- {
- // nothing to do here.
- }
-
- void StoreAppropriatelyTypedValue (object obj, object keyObj)
- {
- var ms = CurrentMemberState; // note that this retrieves parent's current property for EndObject.
- if (ms != null) {
- var state = object_states.Peek ();
- var parent = state.Value;
- var xt = state.Type;
- var xm = ms.Member;
- if (xm == XamlLanguage.Initialization) {
- state.Value = GetCorrectlyTypedValue (null, xt, obj);
- state.IsInstantiated = true;
- } else if (xm.IsEvent) {
- ms.Value = (string) obj; // save name of value delegate (method).
- state.IsInstantiated = true;
- } else if (xm.Type.IsXData) {
- var xdata = (XData) obj;
- var ixser = xm.Invoker.GetValue (state.Value) as IXmlSerializable;
- if (ixser != null)
- ixser.ReadXml ((XmlReader) xdata.XmlReader);
- }
- else if (xm == XamlLanguage.Base)
- ms.Value = GetCorrectlyTypedValue (null, xm.Type, obj);
- else if (xm == XamlLanguage.Name || xm == xt.GetAliasedProperty (XamlLanguage.Name))
- ms.Value = GetCorrectlyTypedValue (xm, XamlLanguage.String, obj);
- else if (xm == XamlLanguage.Key)
- state.KeyValue = GetCorrectlyTypedValue (null, xt.KeyType, obj);
- else {
- if (!AddToCollectionIfAppropriate (xt, xm, parent, obj, keyObj)) {
- if (!xm.IsReadOnly)
- ms.Value = GetCorrectlyTypedValue (xm, xm.Type, obj);
- }
- }
- }
- }
- bool AddToCollectionIfAppropriate (XamlType xt, XamlMember xm, object parent, object obj, object keyObj)
- {
- var mt = xm.Type;
- if (xm == XamlLanguage.Items ||
- xm == XamlLanguage.PositionalParameters ||
- xm == XamlLanguage.Arguments) {
- if (xt.IsDictionary)
- mt.Invoker.AddToDictionary (parent, GetCorrectlyTypedValue (null, xt.KeyType, keyObj), GetCorrectlyTypedValue (null, xt.ItemType, obj));
- else // collection. Note that state.Type isn't usable for PositionalParameters to identify collection kind.
- mt.Invoker.AddToCollection (parent, GetCorrectlyTypedValue (null, xt.ItemType, obj));
- return true;
- }
- else
- return false;
- }
- object GetCorrectlyTypedValue (XamlMember xm, XamlType xt, object value)
- {
- try {
- return DoGetCorrectlyTypedValue (xm, xt, value);
- } catch (XamlObjectWriterException) {
- throw;
- } catch (Exception ex) {
- // For + ex.Message, the runtime should print InnerException message like .NET does.
- 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);
- }
- }
- // It expects that it is not invoked when there is no value to
- // assign.
- // When it is passed null, then it returns a default instance.
- // For example, passing null as Int32 results in 0.
- // But do not immediately try to instantiate with the type, since the type might be abstract.
- object DoGetCorrectlyTypedValue (XamlMember xm, XamlType xt, object value)
- {
- if (value == null) {
- if (xt.IsContentValue (service_provider)) // it is for collection/dictionary key and item
- return null;
- else
- return xt.IsNullable ? null : xt.Invoker.CreateInstance (new object [0]);
- }
- if (xt == null)
- return value;
- // Not sure if this is really required though...
- var vt = sctx.GetXamlType (value.GetType ());
- if (vt.CanAssignTo (xt))
- return value;
- // FIXME: this could be generalized by some means, but I cannot find any.
- if (xt.UnderlyingType == typeof (XamlType) && value is string)
- value = ResolveTypeFromName ((string) value);
- // FIXME: this could be generalized by some means, but I cannot find any.
- if (xt.UnderlyingType == typeof (Type))
- value = new TypeExtension ((string) value).ProvideValue (service_provider);
- if (xt == XamlLanguage.Type && value is string)
- value = new TypeExtension ((string) value);
-
- if (IsAllowedType (xt, value))
- return value;
- var xtc = (xm != null ? xm.TypeConverter : null) ?? xt.TypeConverter;
- if (xtc != null && value != null) {
- var tc = xtc.ConverterInstance;
- if (tc != null && tc.CanConvertFrom (value.GetType ()))
- value = tc.ConvertFrom (value);
- return value;
- }
- throw new XamlObjectWriterException (String.Format ("Value '{0}' (of type {1}) is not of or convertible to type {0} (member {3})", value, value != null ? (object) value.GetType () : "(null)", xt, xm));
- }
- XamlType ResolveTypeFromName (string name)
- {
- var nsr = (IXamlNamespaceResolver) service_provider.GetService (typeof (IXamlNamespaceResolver));
- return sctx.GetXamlType (XamlTypeName.Parse (name, nsr));
- }
- bool IsAllowedType (XamlType xt, object value)
- {
- return xt == null ||
- xt.UnderlyingType == null ||
- xt.UnderlyingType.IsInstanceOfType (value) ||
- value == null && xt == XamlLanguage.Null ||
- xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
- }
-
- void InitializeObjectIfRequired (bool waitForParameters)
- {
- var state = object_states.Peek ();
- if (state.IsInstantiated)
- return;
- if (waitForParameters && (state.Type.ConstructionRequiresArguments || state.Type.HasPositionalParameters (service_provider)))
- return;
- // 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."
- // http://msdn.microsoft.com/en-us/library/system.xaml.xamllanguage.factorymethod%28VS.100%29.aspx
- object obj;
- if (state.FactoryMethod != null) // FIXME: it must be implemented and verified with tests.
- throw new NotImplementedException ();
- else
- obj = state.Type.Invoker.CreateInstance (null);
- state.Value = obj;
- state.IsInstantiated = true;
- if (state.Type.IsAmbient)
- ambient_provider.Push (new AmbientPropertyValue (CurrentMember, obj));
- else
- HandleBeginInit (obj);
- }
- internal IXamlNameResolver name_resolver {
- get { return (IXamlNameResolver) service_provider.GetService (typeof (IXamlNameResolver)); }
- }
- internal override IAmbientProvider AmbientProvider {
- get { return ambient_provider; }
- }
- void ResolvePendingReferences ()
- {
- foreach (var fixup in pending_name_references) {
- foreach (var name in fixup.Names) {
- bool isFullyInitialized;
- // FIXME: sort out relationship between name_scope and name_resolver. (unify to name_resolver, probably)
- var obj = name_scope.FindName (name) ?? name_resolver.Resolve (name, out isFullyInitialized);
- if (obj == null)
- throw new XamlObjectWriterException (String.Format ("Unresolved object reference '{0}' was found", name));
- if (!AddToCollectionIfAppropriate (fixup.ParentType, fixup.ParentMember, fixup.ParentValue, obj, null)) // FIXME: is keyObj always null?
- SetValue (fixup.ParentMember, fixup.ParentValue, obj);
- }
- }
- }
-
- void HandleBeginInit (object value)
- {
- var si = value as ISupportInitialize;
- if (si == null)
- return;
- si.BeginInit ();
- source.OnAfterBeginInit (value);
- }
-
- void HandleEndInit (object value)
- {
- var si = value as ISupportInitialize;
- if (si == null)
- return;
- si.EndInit ();
- source.OnAfterEndInit (value);
- }
- }
- }