1 //------------------------------------------------------------------------------
2 // <copyright file="XmlExtensionFunction.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System.Collections.Generic;
10 using System.Xml.Schema;
11 using System.Reflection;
12 using System.Globalization;
13 using System.Diagnostics;
15 namespace System.Xml.Xsl.Runtime {
16 using Res = System.Xml.Utils.Res;
19 /// Table of bound extension functions. Once an extension function is bound and entered into the table, future bindings
20 /// will be very fast. This table is not thread-safe.
22 internal class XmlExtensionFunctionTable {
23 private Dictionary<XmlExtensionFunction, XmlExtensionFunction> table;
24 private XmlExtensionFunction funcCached;
26 public XmlExtensionFunctionTable() {
27 this.table = new Dictionary<XmlExtensionFunction, XmlExtensionFunction>();
30 public XmlExtensionFunction Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
31 XmlExtensionFunction func;
33 if (this.funcCached == null)
34 this.funcCached = new XmlExtensionFunction();
36 // If the extension function already exists in the table, then binding has already been performed
37 this.funcCached.Init(name, namespaceUri, numArgs, objectType, flags);
38 if (!this.table.TryGetValue(this.funcCached, out func)) {
39 // Function doesn't exist, so bind it and enter it into the table
40 func = this.funcCached;
41 this.funcCached = null;
44 this.table.Add(func, func);
52 /// This internal class contains methods that allow binding to extension functions and invoking them.
54 internal class XmlExtensionFunction {
55 private string namespaceUri; // Extension object identifier
56 private string name; // Name of this method
57 private int numArgs; // Argument count
58 private Type objectType; // Type of the object which will be searched for matching methods
59 private BindingFlags flags; // Modifiers that were used to search for a matching signature
60 private int hashCode; // Pre-computed hashcode
62 private MethodInfo meth; // MethodInfo for extension function
63 private Type[] argClrTypes; // Type array for extension function arguments
64 private Type retClrType; // Type for extension function return value
65 private XmlQueryType[] argXmlTypes; // XmlQueryType array for extension function arguments
66 private XmlQueryType retXmlType; // XmlQueryType for extension function return value
71 public XmlExtensionFunction() {
75 /// Constructor (directly binds to passed MethodInfo).
77 public XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth) {
79 this.namespaceUri = namespaceUri;
86 public XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
87 Init(name, namespaceUri, numArgs, objectType, flags);
91 /// Initialize, but do not bind.
93 public void Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
95 this.namespaceUri = namespaceUri;
96 this.numArgs = numArgs;
97 this.objectType = objectType;
100 this.argClrTypes = null;
101 this.retClrType = null;
102 this.argXmlTypes = null;
103 this.retXmlType = null;
105 // Compute hash code so that it is not recomputed each time GetHashCode() is called
106 this.hashCode = namespaceUri.GetHashCode() ^ name.GetHashCode() ^ ((int) flags << 16) ^ (int) numArgs;
110 /// Once Bind has been successfully called, Method will be non-null.
112 public MethodInfo Method {
113 get { return this.meth; }
117 /// Once Bind has been successfully called, the Clr type of each argument can be accessed.
118 /// Note that this may be different than Method.GetParameterInfo().ParameterType.
120 public Type GetClrArgumentType(int index) {
121 return this.argClrTypes[index];
125 /// Once Bind has been successfully called, the Clr type of the return value can be accessed.
126 /// Note that this may be different than Method.GetParameterInfo().ReturnType.
128 public Type ClrReturnType {
129 get { return this.retClrType; }
133 /// Once Bind has been successfully called, the inferred Xml types of the arguments can be accessed.
135 public XmlQueryType GetXmlArgumentType(int index) {
136 return this.argXmlTypes[index];
140 /// Once Bind has been successfully called, the inferred Xml type of the return value can be accessed.
142 public XmlQueryType XmlReturnType {
143 get { return this.retXmlType; }
147 /// Return true if the CLR type specified in the Init() call has a matching method.
149 public bool CanBind() {
150 MethodInfo[] methods = this.objectType.GetMethods(this.flags);
151 bool ignoreCase = (this.flags & BindingFlags.IgnoreCase) != 0;
152 StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
154 // Find method in object type
155 foreach (MethodInfo methSearch in methods) {
156 if (methSearch.Name.Equals(this.name, comparison) && (this.numArgs == -1 || methSearch.GetParameters().Length == this.numArgs)) {
157 // Binding to generic methods will never succeed
158 if (!methSearch.IsGenericMethodDefinition)
167 /// Bind to the CLR type specified in the Init() call. If a matching method cannot be found, throw an exception.
170 MethodInfo[] methods = this.objectType.GetMethods(this.flags);
171 MethodInfo methMatch = null;
172 bool ignoreCase = (this.flags & BindingFlags.IgnoreCase) != 0;
173 StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
175 // Find method in object type
176 foreach (MethodInfo methSearch in methods) {
177 if (methSearch.Name.Equals(this.name, comparison) && (this.numArgs == -1 || methSearch.GetParameters().Length == this.numArgs)) {
178 if (methMatch != null)
179 throw new XslTransformException(/*[XT_037]*/Res.XmlIl_AmbiguousExtensionMethod, this.namespaceUri, this.name, this.numArgs.ToString(CultureInfo.InvariantCulture));
181 methMatch = methSearch;
185 if (methMatch == null) {
186 methods = this.objectType.GetMethods(this.flags | BindingFlags.NonPublic);
187 foreach (MethodInfo methSearch in methods) {
188 if (methSearch.Name.Equals(this.name, comparison) && methSearch.GetParameters().Length == this.numArgs)
189 throw new XslTransformException(/*[XT_038]*/Res.XmlIl_NonPublicExtensionMethod, this.namespaceUri, this.name);
191 throw new XslTransformException(/*[XT_039]*/Res.XmlIl_NoExtensionMethod, this.namespaceUri, this.name, this.numArgs.ToString(CultureInfo.InvariantCulture));
194 if (methMatch.IsGenericMethodDefinition)
195 throw new XslTransformException(/*[XT_040]*/Res.XmlIl_GenericExtensionMethod, this.namespaceUri, this.name);
197 Debug.Assert(methMatch.ContainsGenericParameters == false);
203 /// Bind to the specified MethodInfo.
205 private void Bind(MethodInfo meth) {
206 ParameterInfo[] paramInfo = meth.GetParameters();
209 // Save the MethodInfo
212 // Get the Clr type of each parameter
213 this.argClrTypes = new Type[paramInfo.Length];
214 for (i = 0; i < paramInfo.Length; i++)
215 this.argClrTypes[i] = GetClrType(paramInfo[i].ParameterType);
217 // Get the Clr type of the return value
218 this.retClrType = GetClrType(this.meth.ReturnType);
220 // Infer an Xml type for each Clr type
221 this.argXmlTypes = new XmlQueryType[paramInfo.Length];
222 for (i = 0; i < paramInfo.Length; i++) {
223 this.argXmlTypes[i] = InferXmlType(this.argClrTypes[i]);
232 if (this.namespaceUri.Length == 0) {
233 if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeNotRtf)
234 this.argXmlTypes[i] = XmlQueryTypeFactory.Node;
235 else if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeSDod)
236 this.argXmlTypes[i] = XmlQueryTypeFactory.NodeS;
239 if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeSDod)
240 this.argXmlTypes[i] = XmlQueryTypeFactory.NodeNotRtfS;
244 // Infer an Xml type for the return Clr type
245 this.retXmlType = InferXmlType(this.retClrType);
249 /// Convert the incoming arguments to an array of CLR objects, and then invoke the external function on the "extObj" object instance.
251 public object Invoke(object extObj, object[] args) {
252 Debug.Assert(this.meth != null, "Must call Bind() before calling Invoke.");
253 Debug.Assert(args.Length == this.argClrTypes.Length, "Mismatched number of actual and formal arguments.");
256 return this.meth.Invoke(extObj, this.flags, null, args, CultureInfo.InvariantCulture);
258 catch (TargetInvocationException e) {
259 throw new XslTransformException(e.InnerException, Res.XmlIl_ExtensionError, this.name);
261 catch (Exception e) {
262 if (!XmlException.IsCatchableException(e)) {
265 throw new XslTransformException(e, Res.XmlIl_ExtensionError, this.name);
270 /// Return true if this XmlExtensionFunction has the same values as another XmlExtensionFunction.
272 public override bool Equals(object other) {
273 XmlExtensionFunction that = other as XmlExtensionFunction;
274 Debug.Assert(that != null);
276 // Compare name, argument count, object type, and binding flags
277 return (this.hashCode == that.hashCode && this.name == that.name && this.namespaceUri == that.namespaceUri &&
278 this.numArgs == that.numArgs && this.objectType == that.objectType && this.flags == that.flags);
282 /// Return this object's hash code, previously computed for performance.
284 public override int GetHashCode() {
285 return this.hashCode;
289 /// 1. Map enumerations to the underlying integral type.
290 /// 2. Throw an exception if the type is ByRef
292 private Type GetClrType(Type clrType) {
294 return Enum.GetUnderlyingType(clrType);
297 throw new XslTransformException(/*[XT_050]*/Res.XmlIl_ByRefType, this.namespaceUri, this.name);
303 /// Infer an Xml type from a Clr type using Xslt infererence rules
305 private XmlQueryType InferXmlType(Type clrType) {
306 return XsltConvert.InferXsltType(clrType);