Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

627 lines
22 KiB
C#

//
// 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);
}
}
}