/ModularEngine/src/DocsByReflection/DocsByReflection.cs

# · C# · 225 lines · 135 code · 26 blank · 64 comment · 13 complexity · ce5bab9ae046661434fc62692ab4115c MD5 · raw file

  1. //Except where stated all code and programs in this project are the copyright of Jim Blackler, 2008.
  2. //jimblackler@gmail.com
  3. //
  4. //This is free software. Libraries and programs are distributed under the terms of the GNU Lesser
  5. //General Public License. Please see the files COPYING and COPYING.LESSER.
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Reflection;
  10. using System.Xml;
  11. namespace JimBlackler.DocsByReflection
  12. {
  13. /// <summary>
  14. /// Utility class to provide documentation for various types where available with the assembly
  15. /// </summary>
  16. public class DocsByReflection
  17. {
  18. /// <summary>
  19. /// Provides the documentation comments for a specific method
  20. /// </summary>
  21. /// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
  22. /// <returns>The XML fragment describing the method</returns>
  23. public static XmlElement XMLFromMember(MethodInfo methodInfo)
  24. {
  25. string parametersString = ParametersToName(methodInfo.GetParameters());
  26. //AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
  27. if (parametersString.Length > 0)
  28. return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
  29. else
  30. return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
  31. }
  32. /// <summary>
  33. /// Converts an array of parameters to its string representation
  34. /// </summary>
  35. /// <param name="parameters">An array of parameters</param>
  36. /// <returns>The string representation of the given parameters</returns>
  37. public static string ParametersToName(ParameterInfo[] parameters)
  38. {
  39. // Calculate the parameter string as this is in the member name in the XML
  40. string ret = "";
  41. foreach (ParameterInfo parameterInfo in parameters)
  42. {
  43. if (ret.Length > 0)
  44. ret += ",";
  45. ret += TypeToName(parameterInfo.ParameterType);
  46. }
  47. return ret;
  48. }
  49. /// <summary>
  50. /// Converts a (possibly generic) type to its string representation
  51. /// </summary>
  52. /// <param name="type">A System.Type that needs to be represented as string</param>
  53. /// <returns>The string representation of the given type</returns>
  54. public static string TypeToName(Type type)
  55. {
  56. if (type.IsGenericType) {
  57. string ret = "";
  58. ret += type.Namespace;
  59. ret += ".";
  60. if (type.Name.Contains("`1"))
  61. ret += type.Name.Substring(0, type.Name.IndexOf('`'));
  62. else
  63. ret += type.Name;
  64. ret += "{";
  65. string typeParams = "";
  66. foreach (Type paramType in type.GetGenericArguments()) {
  67. if (typeParams.Length > 0)
  68. typeParams += ",";
  69. typeParams += TypeToName(paramType);
  70. }
  71. ret += typeParams;
  72. ret += "}";
  73. return ret;
  74. } else {
  75. return type.FullName;
  76. }
  77. }
  78. /// <summary>
  79. /// Provides the documentation comments for a specific member
  80. /// </summary>
  81. /// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
  82. /// <returns>The XML fragment describing the member</returns>
  83. public static XmlElement XMLFromMember(MemberInfo memberInfo)
  84. {
  85. // First character [0] of member type is prefix character in the name in the XML
  86. return XMLFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
  87. }
  88. /// <summary>
  89. /// Provides the documentation comments for a specific type
  90. /// </summary>
  91. /// <param name="type">Type to find the documentation for</param>
  92. /// <returns>The XML fragment that describes the type</returns>
  93. public static XmlElement XMLFromType(Type type)
  94. {
  95. // Prefix in type names is T
  96. return XMLFromName(type, 'T', "");
  97. }
  98. /// <summary>
  99. /// Obtains the XML Element that describes a reflection element by searching the
  100. /// members for a member that has a name that describes the element.
  101. /// </summary>
  102. /// <param name="type">The type or parent type, used to fetch the assembly</param>
  103. /// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
  104. /// <param name="name">Where relevant, the full name qualifier for the element</param>
  105. /// <returns>The member that has a name that describes the specified reflection element</returns>
  106. private static XmlElement XMLFromName(Type type, char prefix, string name)
  107. {
  108. string fullName;
  109. if (String.IsNullOrEmpty(name))
  110. {
  111. fullName = prefix + ":" + type.FullName;
  112. }
  113. else
  114. {
  115. fullName = prefix + ":" + type.FullName + "." + name;
  116. }
  117. fullName = fullName.Replace("+", ".");
  118. XmlDocument xmlDocument = XMLFromAssembly(type.Assembly);
  119. XmlElement matchedElement = null;
  120. foreach (XmlElement xmlElement in xmlDocument["doc"]["members"])
  121. {
  122. if (xmlElement.Attributes["name"].Value.Equals(fullName))
  123. {
  124. if (matchedElement != null)
  125. {
  126. throw new DocsByReflectionException("Multiple matches to query", null);
  127. }
  128. matchedElement = xmlElement;
  129. }
  130. }
  131. return matchedElement;
  132. }
  133. /// <summary>
  134. /// A cache used to remember Xml documentation for assemblies
  135. /// </summary>
  136. static Dictionary<Assembly, XmlDocument> cache = new Dictionary<Assembly, XmlDocument>();
  137. /// <summary>
  138. /// A cache used to store failure exceptions for assembly lookups
  139. /// </summary>
  140. static Dictionary<Assembly, Exception> failCache = new Dictionary<Assembly, Exception>();
  141. /// <summary>
  142. /// Obtains the documentation file for the specified assembly
  143. /// </summary>
  144. /// <param name="assembly">The assembly to find the XML document for</param>
  145. /// <returns>The XML document</returns>
  146. /// <remarks>This version uses a cache to preserve the assemblies, so that
  147. /// the XML file is not loaded and parsed on every single lookup</remarks>
  148. public static XmlDocument XMLFromAssembly(Assembly assembly)
  149. {
  150. if (failCache.ContainsKey(assembly))
  151. {
  152. throw failCache[assembly];
  153. }
  154. try
  155. {
  156. if (!cache.ContainsKey(assembly))
  157. {
  158. // load the docuemnt into the cache
  159. cache[assembly] = XMLFromAssemblyNonCached(assembly);
  160. }
  161. return cache[assembly];
  162. }
  163. catch (Exception exception)
  164. {
  165. failCache[assembly] = exception;
  166. throw exception;
  167. }
  168. }
  169. /// <summary>
  170. /// Loads and parses the documentation file for the specified assembly
  171. /// </summary>
  172. /// <param name="assembly">The assembly to find the XML document for</param>
  173. /// <returns>The XML document</returns>
  174. private static XmlDocument XMLFromAssemblyNonCached(Assembly assembly)
  175. {
  176. string assemblyFilename = assembly.CodeBase;
  177. const string prefix = "file:///";
  178. if (assemblyFilename.StartsWith(prefix))
  179. {
  180. StreamReader streamReader;
  181. try
  182. {
  183. streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename.Substring(prefix.Length), ".xml"));
  184. }
  185. catch (FileNotFoundException exception)
  186. {
  187. throw new DocsByReflectionException("XML documentation not present (make sure it is turned on in project properties when building)", exception);
  188. }
  189. XmlDocument xmlDocument = new XmlDocument();
  190. xmlDocument.Load(streamReader);
  191. return xmlDocument;
  192. }
  193. else
  194. {
  195. throw new DocsByReflectionException("Could not ascertain assembly filename", null);
  196. }
  197. }
  198. }
  199. }