1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Xml; 8 using System.Xml.Schema; 9 using System.Reflection; 10 using System.Globalization; 11 using System.Diagnostics; 12 13 namespace System.Xml.Xsl.Runtime 14 { 15 /// <summary> 16 /// Table of bound extension functions. Once an extension function is bound and entered into the table, future bindings 17 /// will be very fast. This table is not thread-safe. 18 /// </summary> 19 internal class XmlExtensionFunctionTable 20 { 21 private Dictionary<XmlExtensionFunction, XmlExtensionFunction> _table; 22 private XmlExtensionFunction _funcCached; 23 XmlExtensionFunctionTable()24 public XmlExtensionFunctionTable() 25 { 26 _table = new Dictionary<XmlExtensionFunction, XmlExtensionFunction>(); 27 } 28 Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)29 public XmlExtensionFunction Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) 30 { 31 XmlExtensionFunction func; 32 33 if (_funcCached == null) 34 _funcCached = new XmlExtensionFunction(); 35 36 // If the extension function already exists in the table, then binding has already been performed 37 _funcCached.Init(name, namespaceUri, numArgs, objectType, flags); 38 if (!_table.TryGetValue(_funcCached, out func)) 39 { 40 // Function doesn't exist, so bind it and enter it into the table 41 func = _funcCached; 42 _funcCached = null; 43 44 func.Bind(); 45 _table.Add(func, func); 46 } 47 48 return func; 49 } 50 } 51 52 /// <summary> 53 /// This internal class contains methods that allow binding to extension functions and invoking them. 54 /// </summary> 55 internal class XmlExtensionFunction 56 { 57 private string _namespaceUri; // Extension object identifier 58 private string _name; // Name of this method 59 private int _numArgs; // Argument count 60 private Type _objectType; // Type of the object which will be searched for matching methods 61 private BindingFlags _flags; // Modifiers that were used to search for a matching signature 62 private int _hashCode; // Pre-computed hashcode 63 64 private MethodInfo _meth; // MethodInfo for extension function 65 private Type[] _argClrTypes; // Type array for extension function arguments 66 private Type _retClrType; // Type for extension function return value 67 private XmlQueryType[] _argXmlTypes; // XmlQueryType array for extension function arguments 68 private XmlQueryType _retXmlType; // XmlQueryType for extension function return value 69 70 /// <summary> 71 /// Constructor. 72 /// </summary> XmlExtensionFunction()73 public XmlExtensionFunction() 74 { 75 } 76 77 /// <summary> 78 /// Constructor (directly binds to passed MethodInfo). 79 /// </summary> XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth)80 public XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth) 81 { 82 _name = name; 83 _namespaceUri = namespaceUri; 84 Bind(meth); 85 } 86 87 /// <summary> 88 /// Constructor. 89 /// </summary> XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)90 public XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) 91 { 92 Init(name, namespaceUri, numArgs, objectType, flags); 93 } 94 95 /// <summary> 96 /// Initialize, but do not bind. 97 /// </summary> Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)98 public void Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags) 99 { 100 _name = name; 101 _namespaceUri = namespaceUri; 102 _numArgs = numArgs; 103 _objectType = objectType; 104 _flags = flags; 105 _meth = null; 106 _argClrTypes = null; 107 _retClrType = null; 108 _argXmlTypes = null; 109 _retXmlType = null; 110 111 // Compute hash code so that it is not recomputed each time GetHashCode() is called 112 _hashCode = namespaceUri.GetHashCode() ^ name.GetHashCode() ^ ((int)flags << 16) ^ (int)numArgs; 113 } 114 115 /// <summary> 116 /// Once Bind has been successfully called, Method will be non-null. 117 /// </summary> 118 public MethodInfo Method 119 { 120 get { return _meth; } 121 } 122 123 /// <summary> 124 /// Once Bind has been successfully called, the Clr type of each argument can be accessed. 125 /// Note that this may be different than Method.GetParameterInfo().ParameterType. 126 /// </summary> GetClrArgumentType(int index)127 public Type GetClrArgumentType(int index) 128 { 129 return _argClrTypes[index]; 130 } 131 132 /// <summary> 133 /// Once Bind has been successfully called, the Clr type of the return value can be accessed. 134 /// Note that this may be different than Method.GetParameterInfo().ReturnType. 135 /// </summary> 136 public Type ClrReturnType 137 { 138 get { return _retClrType; } 139 } 140 141 /// <summary> 142 /// Once Bind has been successfully called, the inferred Xml types of the arguments can be accessed. 143 /// </summary> GetXmlArgumentType(int index)144 public XmlQueryType GetXmlArgumentType(int index) 145 { 146 return _argXmlTypes[index]; 147 } 148 149 /// <summary> 150 /// Once Bind has been successfully called, the inferred Xml type of the return value can be accessed. 151 /// </summary> 152 public XmlQueryType XmlReturnType 153 { 154 get { return _retXmlType; } 155 } 156 157 /// <summary> 158 /// Return true if the CLR type specified in the Init() call has a matching method. 159 /// </summary> CanBind()160 public bool CanBind() 161 { 162 MethodInfo[] methods = _objectType.GetMethods(_flags); 163 bool ignoreCase = (_flags & BindingFlags.IgnoreCase) != 0; 164 StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 165 166 // Find method in object type 167 foreach (MethodInfo methSearch in methods) 168 { 169 if (methSearch.Name.Equals(_name, comparison) && (_numArgs == -1 || methSearch.GetParameters().Length == _numArgs)) 170 { 171 // Binding to generic methods will never succeed 172 if (!methSearch.IsGenericMethodDefinition) 173 return true; 174 } 175 } 176 177 return false; 178 } 179 180 /// <summary> 181 /// Bind to the CLR type specified in the Init() call. If a matching method cannot be found, throw an exception. 182 /// </summary> Bind()183 public void Bind() 184 { 185 MethodInfo[] methods = _objectType.GetMethods(_flags); 186 MethodInfo methMatch = null; 187 bool ignoreCase = (_flags & BindingFlags.IgnoreCase) != 0; 188 StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 189 190 // Find method in object type 191 foreach (MethodInfo methSearch in methods) 192 { 193 if (methSearch.Name.Equals(_name, comparison) && (_numArgs == -1 || methSearch.GetParameters().Length == _numArgs)) 194 { 195 if (methMatch != null) 196 throw new XslTransformException(/*[XT_037]*/SR.XmlIl_AmbiguousExtensionMethod, _namespaceUri, _name, _numArgs.ToString(CultureInfo.InvariantCulture)); 197 198 methMatch = methSearch; 199 } 200 } 201 202 if (methMatch == null) 203 { 204 methods = _objectType.GetMethods(_flags | BindingFlags.NonPublic); 205 foreach (MethodInfo methSearch in methods) 206 { 207 if (methSearch.Name.Equals(_name, comparison) && methSearch.GetParameters().Length == _numArgs) 208 throw new XslTransformException(/*[XT_038]*/SR.XmlIl_NonPublicExtensionMethod, _namespaceUri, _name); 209 } 210 throw new XslTransformException(/*[XT_039]*/SR.XmlIl_NoExtensionMethod, _namespaceUri, _name, _numArgs.ToString(CultureInfo.InvariantCulture)); 211 } 212 213 if (methMatch.IsGenericMethodDefinition) 214 throw new XslTransformException(/*[XT_040]*/SR.XmlIl_GenericExtensionMethod, _namespaceUri, _name); 215 216 Debug.Assert(methMatch.ContainsGenericParameters == false); 217 218 Bind(methMatch); 219 } 220 221 /// <summary> 222 /// Bind to the specified MethodInfo. 223 /// </summary> Bind(MethodInfo meth)224 private void Bind(MethodInfo meth) 225 { 226 ParameterInfo[] paramInfo = meth.GetParameters(); 227 int i; 228 229 // Save the MethodInfo 230 _meth = meth; 231 232 // Get the Clr type of each parameter 233 _argClrTypes = new Type[paramInfo.Length]; 234 for (i = 0; i < paramInfo.Length; i++) 235 _argClrTypes[i] = GetClrType(paramInfo[i].ParameterType); 236 237 // Get the Clr type of the return value 238 _retClrType = GetClrType(_meth.ReturnType); 239 240 // Infer an Xml type for each Clr type 241 _argXmlTypes = new XmlQueryType[paramInfo.Length]; 242 for (i = 0; i < paramInfo.Length; i++) 243 { 244 _argXmlTypes[i] = InferXmlType(_argClrTypes[i]); 245 246 // BUGBUG: 247 // 1. A couple built-in Xslt functions allow Rtf as argument, which is 248 // different from what InferXmlType returns. Until XsltEarlyBound references 249 // a Qil function, we'll work around this case by assuming that all built-in 250 // Xslt functions allow Rtf. 251 // 2. Script arguments should allow node-sets which are not statically known 252 // to be Dod to be passed, so relax static typing in this case. 253 if (_namespaceUri.Length == 0) 254 { 255 if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeNotRtf) 256 _argXmlTypes[i] = XmlQueryTypeFactory.Node; 257 else if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeSDod) 258 _argXmlTypes[i] = XmlQueryTypeFactory.NodeS; 259 } 260 else 261 { 262 if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeSDod) 263 _argXmlTypes[i] = XmlQueryTypeFactory.NodeNotRtfS; 264 } 265 } 266 267 // Infer an Xml type for the return Clr type 268 _retXmlType = InferXmlType(_retClrType); 269 } 270 271 /// <summary> 272 /// Convert the incoming arguments to an array of CLR objects, and then invoke the external function on the "extObj" object instance. 273 /// </summary> Invoke(object extObj, object[] args)274 public object Invoke(object extObj, object[] args) 275 { 276 Debug.Assert(_meth != null, "Must call Bind() before calling Invoke."); 277 Debug.Assert(args.Length == _argClrTypes.Length, "Mismatched number of actual and formal arguments."); 278 279 try 280 { 281 return _meth.Invoke(extObj, args); 282 } 283 catch (TargetInvocationException e) 284 { 285 throw new XslTransformException(e.InnerException, SR.XmlIl_ExtensionError, _name); 286 } 287 catch (Exception e) 288 { 289 if (!XmlException.IsCatchableException(e)) 290 { 291 throw; 292 } 293 throw new XslTransformException(e, SR.XmlIl_ExtensionError, _name); 294 } 295 } 296 297 /// <summary> 298 /// Return true if this XmlExtensionFunction has the same values as another XmlExtensionFunction. 299 /// </summary> Equals(object other)300 public override bool Equals(object other) 301 { 302 XmlExtensionFunction that = other as XmlExtensionFunction; 303 Debug.Assert(that != null); 304 305 // Compare name, argument count, object type, and binding flags 306 return (_hashCode == that._hashCode && _name == that._name && _namespaceUri == that._namespaceUri && 307 _numArgs == that._numArgs && _objectType == that._objectType && _flags == that._flags); 308 } 309 310 /// <summary> 311 /// Return this object's hash code, previously computed for performance. 312 /// </summary> GetHashCode()313 public override int GetHashCode() 314 { 315 return _hashCode; 316 } 317 318 /// <summary> 319 /// 1. Map enumerations to the underlying integral type. 320 /// 2. Throw an exception if the type is ByRef 321 /// </summary> GetClrType(Type clrType)322 private Type GetClrType(Type clrType) 323 { 324 if (clrType.IsEnum) 325 return Enum.GetUnderlyingType(clrType); 326 327 if (clrType.IsByRef) 328 throw new XslTransformException(/*[XT_050]*/SR.XmlIl_ByRefType, _namespaceUri, _name); 329 330 return clrType; 331 } 332 333 /// <summary> 334 /// Infer an Xml type from a Clr type using Xslt inference rules 335 /// </summary> InferXmlType(Type clrType)336 private XmlQueryType InferXmlType(Type clrType) 337 { 338 return XsltConvert.InferXsltType(clrType); 339 } 340 } 341 } 342