//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] //------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.Xml; using System.Xml.Schema; using System.Reflection; using System.Globalization; using System.Diagnostics; namespace System.Xml.Xsl.Runtime { using Res = System.Xml.Utils.Res; /// /// Table of bound extension functions. Once an extension function is bound and entered into the table, future bindings /// will be very fast. This table is not thread-safe. /// internal class XmlExtensionFunctionTable { private Dictionary table; private XmlExtensionFunction funcCached; public XmlExtensionFunctionTable() { this.table = new Dictionary(); } public XmlExtensionFunction Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) { XmlExtensionFunction func; if (this.funcCached == null) this.funcCached = new XmlExtensionFunction(); // If the extension function already exists in the table, then binding has already been performed this.funcCached.Init(name, namespaceUri, numArgs, objectType, flags); if (!this.table.TryGetValue(this.funcCached, out func)) { // Function doesn't exist, so bind it and enter it into the table func = this.funcCached; this.funcCached = null; func.Bind(); this.table.Add(func, func); } return func; } } /// /// This internal class contains methods that allow binding to extension functions and invoking them. /// internal class XmlExtensionFunction { private string namespaceUri; // Extension object identifier private string name; // Name of this method private int numArgs; // Argument count private Type objectType; // Type of the object which will be searched for matching methods private BindingFlags flags; // Modifiers that were used to search for a matching signature private int hashCode; // Pre-computed hashcode private MethodInfo meth; // MethodInfo for extension function private Type[] argClrTypes; // Type array for extension function arguments private Type retClrType; // Type for extension function return value private XmlQueryType[] argXmlTypes; // XmlQueryType array for extension function arguments private XmlQueryType retXmlType; // XmlQueryType for extension function return value /// /// Constructor. /// public XmlExtensionFunction() { } /// /// Constructor (directly binds to passed MethodInfo). /// public XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth) { this.name = name; this.namespaceUri = namespaceUri; Bind(meth); } /// /// Constructor. /// public XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) { Init(name, namespaceUri, numArgs, objectType, flags); } /// /// Initialize, but do not bind. /// public void Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) { this.name = name; this.namespaceUri = namespaceUri; this.numArgs = numArgs; this.objectType = objectType; this.flags = flags; this.meth = null; this.argClrTypes = null; this.retClrType = null; this.argXmlTypes = null; this.retXmlType = null; // Compute hash code so that it is not recomputed each time GetHashCode() is called this.hashCode = namespaceUri.GetHashCode() ^ name.GetHashCode() ^ ((int) flags << 16) ^ (int) numArgs; } /// /// Once Bind has been successfully called, Method will be non-null. /// public MethodInfo Method { get { return this.meth; } } /// /// Once Bind has been successfully called, the Clr type of each argument can be accessed. /// Note that this may be different than Method.GetParameterInfo().ParameterType. /// public Type GetClrArgumentType(int index) { return this.argClrTypes[index]; } /// /// Once Bind has been successfully called, the Clr type of the return value can be accessed. /// Note that this may be different than Method.GetParameterInfo().ReturnType. /// public Type ClrReturnType { get { return this.retClrType; } } /// /// Once Bind has been successfully called, the inferred Xml types of the arguments can be accessed. /// public XmlQueryType GetXmlArgumentType(int index) { return this.argXmlTypes[index]; } /// /// Once Bind has been successfully called, the inferred Xml type of the return value can be accessed. /// public XmlQueryType XmlReturnType { get { return this.retXmlType; } } /// /// Return true if the CLR type specified in the Init() call has a matching method. /// public bool CanBind() { MethodInfo[] methods = this.objectType.GetMethods(this.flags); bool ignoreCase = (this.flags & BindingFlags.IgnoreCase) != 0; StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; // Find method in object type foreach (MethodInfo methSearch in methods) { if (methSearch.Name.Equals(this.name, comparison) && (this.numArgs == -1 || methSearch.GetParameters().Length == this.numArgs)) { // Binding to generic methods will never succeed if (!methSearch.IsGenericMethodDefinition) return true; } } return false; } /// /// Bind to the CLR type specified in the Init() call. If a matching method cannot be found, throw an exception. /// public void Bind() { MethodInfo[] methods = this.objectType.GetMethods(this.flags); MethodInfo methMatch = null; bool ignoreCase = (this.flags & BindingFlags.IgnoreCase) != 0; StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; // Find method in object type foreach (MethodInfo methSearch in methods) { if (methSearch.Name.Equals(this.name, comparison) && (this.numArgs == -1 || methSearch.GetParameters().Length == this.numArgs)) { if (methMatch != null) throw new XslTransformException(/*[XT_037]*/Res.XmlIl_AmbiguousExtensionMethod, this.namespaceUri, this.name, this.numArgs.ToString(CultureInfo.InvariantCulture)); methMatch = methSearch; } } if (methMatch == null) { methods = this.objectType.GetMethods(this.flags | BindingFlags.NonPublic); foreach (MethodInfo methSearch in methods) { if (methSearch.Name.Equals(this.name, comparison) && methSearch.GetParameters().Length == this.numArgs) throw new XslTransformException(/*[XT_038]*/Res.XmlIl_NonPublicExtensionMethod, this.namespaceUri, this.name); } throw new XslTransformException(/*[XT_039]*/Res.XmlIl_NoExtensionMethod, this.namespaceUri, this.name, this.numArgs.ToString(CultureInfo.InvariantCulture)); } if (methMatch.IsGenericMethodDefinition) throw new XslTransformException(/*[XT_040]*/Res.XmlIl_GenericExtensionMethod, this.namespaceUri, this.name); Debug.Assert(methMatch.ContainsGenericParameters == false); Bind(methMatch); } /// /// Bind to the specified MethodInfo. /// private void Bind(MethodInfo meth) { ParameterInfo[] paramInfo = meth.GetParameters(); int i; // Save the MethodInfo this.meth = meth; // Get the Clr type of each parameter this.argClrTypes = new Type[paramInfo.Length]; for (i = 0; i < paramInfo.Length; i++) this.argClrTypes[i] = GetClrType(paramInfo[i].ParameterType); // Get the Clr type of the return value this.retClrType = GetClrType(this.meth.ReturnType); // Infer an Xml type for each Clr type this.argXmlTypes = new XmlQueryType[paramInfo.Length]; for (i = 0; i < paramInfo.Length; i++) { this.argXmlTypes[i] = InferXmlType(this.argClrTypes[i]); // if (this.namespaceUri.Length == 0) { if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeNotRtf) this.argXmlTypes[i] = XmlQueryTypeFactory.Node; else if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeSDod) this.argXmlTypes[i] = XmlQueryTypeFactory.NodeS; } else { if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeSDod) this.argXmlTypes[i] = XmlQueryTypeFactory.NodeNotRtfS; } } // Infer an Xml type for the return Clr type this.retXmlType = InferXmlType(this.retClrType); } /// /// Convert the incoming arguments to an array of CLR objects, and then invoke the external function on the "extObj" object instance. /// public object Invoke(object extObj, object[] args) { Debug.Assert(this.meth != null, "Must call Bind() before calling Invoke."); Debug.Assert(args.Length == this.argClrTypes.Length, "Mismatched number of actual and formal arguments."); try { return this.meth.Invoke(extObj, this.flags, null, args, CultureInfo.InvariantCulture); } catch (TargetInvocationException e) { throw new XslTransformException(e.InnerException, Res.XmlIl_ExtensionError, this.name); } catch (Exception e) { if (!XmlException.IsCatchableException(e)) { throw; } throw new XslTransformException(e, Res.XmlIl_ExtensionError, this.name); } } /// /// Return true if this XmlExtensionFunction has the same values as another XmlExtensionFunction. /// public override bool Equals(object other) { XmlExtensionFunction that = other as XmlExtensionFunction; Debug.Assert(that != null); // Compare name, argument count, object type, and binding flags return (this.hashCode == that.hashCode && this.name == that.name && this.namespaceUri == that.namespaceUri && this.numArgs == that.numArgs && this.objectType == that.objectType && this.flags == that.flags); } /// /// Return this object's hash code, previously computed for performance. /// public override int GetHashCode() { return this.hashCode; } /// /// 1. Map enumerations to the underlying integral type. /// 2. Throw an exception if the type is ByRef /// private Type GetClrType(Type clrType) { if (clrType.IsEnum) return Enum.GetUnderlyingType(clrType); if (clrType.IsByRef) throw new XslTransformException(/*[XT_050]*/Res.XmlIl_ByRefType, this.namespaceUri, this.name); return clrType; } /// /// Infer an Xml type from a Clr type using Xslt infererence rules /// private XmlQueryType InferXmlType(Type clrType) { return XsltConvert.InferXsltType(clrType); } } }