Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Runtime / XmlExtensionFunction.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlExtensionFunction.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.Collections.Generic;
9 using System.Xml;
10 using System.Xml.Schema;
11 using System.Reflection;
12 using System.Globalization;
13 using System.Diagnostics;
14
15 namespace System.Xml.Xsl.Runtime {
16     using Res           = System.Xml.Utils.Res;
17
18     /// <summary>
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.
21     /// </summary>
22     internal class XmlExtensionFunctionTable {
23         private Dictionary<XmlExtensionFunction, XmlExtensionFunction> table;
24         private XmlExtensionFunction funcCached;
25
26         public XmlExtensionFunctionTable() {
27             this.table = new Dictionary<XmlExtensionFunction, XmlExtensionFunction>();
28         }
29
30         public XmlExtensionFunction Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
31             XmlExtensionFunction func;
32
33             if (this.funcCached == null)
34                 this.funcCached = new XmlExtensionFunction();
35
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;
42
43                 func.Bind();
44                 this.table.Add(func, func);
45             }
46
47             return func;
48         }
49     }
50
51     /// <summary>
52     /// This internal class contains methods that allow binding to extension functions and invoking them.
53     /// </summary>
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
61
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
67
68         /// <summary>
69         /// Constructor.
70         /// </summary>
71         public XmlExtensionFunction() {
72         }
73
74         /// <summary>
75         /// Constructor (directly binds to passed MethodInfo).
76         /// </summary>
77         public XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth) {
78             this.name = name;
79             this.namespaceUri = namespaceUri;
80             Bind(meth);
81         }
82
83         /// <summary>
84         /// Constructor.
85         /// </summary>
86         public XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
87             Init(name, namespaceUri, numArgs, objectType, flags);
88         }
89
90         /// <summary>
91         /// Initialize, but do not bind.
92         /// </summary>
93         public void Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) {
94             this.name = name;
95             this.namespaceUri = namespaceUri;
96             this.numArgs = numArgs;
97             this.objectType = objectType;
98             this.flags = flags;
99             this.meth = null;
100             this.argClrTypes = null;
101             this.retClrType = null;
102             this.argXmlTypes = null;
103             this.retXmlType = null;
104
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;
107         }
108
109         /// <summary>
110         /// Once Bind has been successfully called, Method will be non-null.
111         /// </summary>
112         public MethodInfo Method {
113             get { return this.meth; }
114         }
115
116         /// <summary>
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.
119         /// </summary>
120         public Type GetClrArgumentType(int index) {
121             return this.argClrTypes[index];
122         }
123
124         /// <summary>
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.
127         /// </summary>
128         public Type ClrReturnType {
129             get { return this.retClrType; }
130         }
131
132         /// <summary>
133         /// Once Bind has been successfully called, the inferred Xml types of the arguments can be accessed.
134         /// </summary>
135         public XmlQueryType GetXmlArgumentType(int index) {
136             return this.argXmlTypes[index];
137         }
138
139         /// <summary>
140         /// Once Bind has been successfully called, the inferred Xml type of the return value can be accessed.
141         /// </summary>
142         public XmlQueryType XmlReturnType {
143             get { return this.retXmlType; }
144         }
145
146         /// <summary>
147         /// Return true if the CLR type specified in the Init() call has a matching method.
148         /// </summary>
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;
153
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)
159                         return true;
160                 }
161             }
162
163             return false;
164         }
165
166         /// <summary>
167         /// Bind to the CLR type specified in the Init() call.  If a matching method cannot be found, throw an exception.
168         /// </summary>
169         public void Bind() {
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;
174
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));
180
181                     methMatch = methSearch;
182                 }
183             }
184
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);
190                 }
191                 throw new XslTransformException(/*[XT_039]*/Res.XmlIl_NoExtensionMethod, this.namespaceUri, this.name, this.numArgs.ToString(CultureInfo.InvariantCulture));
192             }
193
194             if (methMatch.IsGenericMethodDefinition)
195                 throw new XslTransformException(/*[XT_040]*/Res.XmlIl_GenericExtensionMethod, this.namespaceUri, this.name);
196
197             Debug.Assert(methMatch.ContainsGenericParameters == false);
198
199             Bind(methMatch);
200         }
201
202         /// <summary>
203         /// Bind to the specified MethodInfo.
204         /// </summary>
205         private void Bind(MethodInfo meth) {
206             ParameterInfo[] paramInfo = meth.GetParameters();
207             int i;
208
209             // Save the MethodInfo
210             this.meth = meth;
211
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);
216
217             // Get the Clr type of the return value
218             this.retClrType = GetClrType(this.meth.ReturnType);
219
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]);
224
225                 // 
226
227
228
229
230
231
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;
237                 }
238                 else {
239                     if ((object) this.argXmlTypes[i] == (object) XmlQueryTypeFactory.NodeSDod)
240                         this.argXmlTypes[i] = XmlQueryTypeFactory.NodeNotRtfS;
241                 }
242             }
243
244             // Infer an Xml type for the return Clr type
245             this.retXmlType = InferXmlType(this.retClrType);
246         }
247
248         /// <summary>
249         /// Convert the incoming arguments to an array of CLR objects, and then invoke the external function on the "extObj" object instance.
250         /// </summary>
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.");
254
255             try {
256                 return this.meth.Invoke(extObj, this.flags, null, args, CultureInfo.InvariantCulture);
257             }
258             catch (TargetInvocationException e) {
259                 throw new XslTransformException(e.InnerException, Res.XmlIl_ExtensionError, this.name);
260             }
261             catch (Exception e) {
262                 if (!XmlException.IsCatchableException(e)) {
263                     throw;
264                 }
265                 throw new XslTransformException(e, Res.XmlIl_ExtensionError, this.name);
266             }
267         }
268
269         /// <summary>
270         /// Return true if this XmlExtensionFunction has the same values as another XmlExtensionFunction.
271         /// </summary>
272         public override bool Equals(object other) {
273             XmlExtensionFunction that = other as XmlExtensionFunction;
274             Debug.Assert(that != null);
275
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);
279         }
280
281         /// <summary>
282         /// Return this object's hash code, previously computed for performance.
283         /// </summary>
284         public override int GetHashCode() {
285             return this.hashCode;
286         }
287
288         /// <summary>
289         /// 1. Map enumerations to the underlying integral type.
290         /// 2. Throw an exception if the type is ByRef
291         /// </summary>
292         private Type GetClrType(Type clrType) {
293             if (clrType.IsEnum)
294                 return Enum.GetUnderlyingType(clrType);
295
296             if (clrType.IsByRef)
297                 throw new XslTransformException(/*[XT_050]*/Res.XmlIl_ByRefType, this.namespaceUri, this.name);
298
299             return clrType;
300         }
301
302         /// <summary>
303         /// Infer an Xml type from a Clr type using Xslt infererence rules
304         /// </summary>
305         private XmlQueryType InferXmlType(Type clrType) {
306             return XsltConvert.InferXsltType(clrType);
307         }
308     }
309 }