The Labs \ Source Viewer \ SSCLI \ System.Xml.Xsl.Runtime \ XmlExtensionFunctionTable

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

Developer Fusion