PageRenderTime 38ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/app/NHtmlUnitGenerator/WrapperClassInfo.cs

https://github.com/Tzunami/NHtmlUnit
C# | 505 lines | 383 code | 102 blank | 20 comment | 48 complexity | 2356d2f7c3641552b3eb11d1c30c9e50 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0
  1. #region License
  2. // --------------------------------------------------
  3. // Copyright © OKB. All Rights Reserved.
  4. //
  5. // This software is proprietary information of OKB.
  6. // USE IS SUBJECT TO LICENSE TERMS.
  7. // --------------------------------------------------
  8. #endregion
  9. using System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Reflection;
  14. using System.Text;
  15. using com.gargoylesoftware.htmlunit;
  16. namespace NHtmlUnit.Generator
  17. {
  18. public class WrapperClassInfo
  19. {
  20. private readonly List<WrapperConstructorInfo> constructors;
  21. private readonly string htmlUnitVersion;
  22. private readonly Dictionary<string, WrapperMethodInfo> methods;
  23. private readonly Dictionary<string, WrapperPropInfo> properties;
  24. private readonly List<WrapperStaticPublicField> staticPublicFields;
  25. private readonly Type wrappedType;
  26. private readonly WrapperRepository wrapperRepository;
  27. public WrapperClassInfo(Type wrappedFromType, WrapperRepository wrapperRepository)
  28. {
  29. this.wrapperRepository = wrapperRepository;
  30. this.wrappedType = wrappedFromType;
  31. this.properties = new Dictionary<string, WrapperPropInfo>();
  32. this.methods = new Dictionary<string, WrapperMethodInfo>();
  33. this.constructors = new List<WrapperConstructorInfo>();
  34. this.staticPublicFields = new List<WrapperStaticPublicField>();
  35. ScanMembers(wrappedFromType);
  36. Assembly htmlUnitAssembly = Assembly.GetAssembly(typeof(WebClient));
  37. this.htmlUnitVersion = htmlUnitAssembly.GetName().Version.ToString();
  38. }
  39. public List<WrapperConstructorInfo> Constructors
  40. {
  41. get { return this.constructors; }
  42. }
  43. public string FilePathBase
  44. {
  45. get
  46. {
  47. var names = RelativeClassName.Split('.');
  48. var directories = new[] { Repository.TargetDirectory, "Generated" }
  49. .Union(names)
  50. .ToArray();
  51. return Path.Combine(directories);
  52. }
  53. }
  54. public WrapperClassInfo InheritsFrom { get; set; }
  55. public bool IsInterface
  56. {
  57. get { return WrappedType.IsInterface; }
  58. }
  59. public Dictionary<string, WrapperMethodInfo> Methods
  60. {
  61. get { return this.methods; }
  62. }
  63. public Dictionary<string, WrapperPropInfo> Properties
  64. {
  65. get { return this.properties; }
  66. }
  67. public string RelativeClassName
  68. {
  69. get { return Repository.GetRelativeClassName(this); }
  70. }
  71. public WrapperRepository Repository
  72. {
  73. get { return this.wrapperRepository; }
  74. }
  75. public List<WrapperStaticPublicField> StaticPublicFields
  76. {
  77. get { return this.staticPublicFields; }
  78. }
  79. public string TargetBaseName
  80. {
  81. get
  82. {
  83. if (WrappedBase != null && Repository.TypeIsWrapped(WrappedBase))
  84. return Repository.GetTargetFullName(WrappedBase);
  85. return "ObjectWrapper";
  86. }
  87. }
  88. public string TargetFullName
  89. {
  90. get { return Repository.GetTargetFullName(this); }
  91. }
  92. public string TargetNameWithoutNamespace
  93. {
  94. get { return TargetFullName.Substring(TargetFullName.LastIndexOf('.') + 1); }
  95. }
  96. public string TargetNamespace
  97. {
  98. get
  99. {
  100. var names = TargetFullName.Split('.')
  101. .Select(x => String.Concat(x.Substring(0, 1).ToUpper(), x.Substring(1)))
  102. .ToArray();
  103. string ns = String.Join(".", names.Take(names.Length - 1));
  104. return ns;
  105. }
  106. }
  107. public Type WrappedBase
  108. {
  109. get { return this.wrappedType.BaseType; }
  110. }
  111. public Type WrappedType
  112. {
  113. get { return this.wrappedType; }
  114. }
  115. //public string EncodedClassName { get { return
  116. public void GenerateClassCode(StringBuilder sb)
  117. {
  118. GeneratePartialClassHeader(sb, false);
  119. sb.AppendFormat(
  120. @" static {0}()
  121. {{
  122. ObjectWrapper.RegisterWrapperCreator(({1} o) =>
  123. new {0}(o));
  124. }}
  125. ",
  126. TargetNameWithoutNamespace,
  127. WrappedType.FullName);
  128. sb.AppendFormat(" public {0}({1} wrappedObject) : base(wrappedObject) {{}}\r\n",
  129. TargetNameWithoutNamespace,
  130. WrappedType.FullName);
  131. sb.AppendLine();
  132. sb.AppendFormat(" public {0}{1} WObj\r\n",
  133. TargetBaseName == "ObjectWrapper" ? "" : "new ",
  134. WrappedType.FullName);
  135. sb.AppendFormat(" {{\r\n get {{ return ({0})WrappedObject; }}\r\n }}\r\n",
  136. WrappedType.FullName);
  137. sb.AppendLine();
  138. foreach (var wsf in StaticPublicFields)
  139. wsf.GenerateStaticFieldWrapper(sb);
  140. foreach (var wc in Constructors)
  141. wc.GenerateConstructorCode(sb);
  142. foreach (var wpi in Properties.Where(ShouldMapAsProperty))
  143. wpi.Value.GeneratePropertyCode(sb);
  144. foreach (var wm in Methods)
  145. wm.Value.GenerateMethodCode(sb);
  146. GeneratePartialClassFooter(sb);
  147. }
  148. public void GenerateInterfaceCode(StringBuilder sb)
  149. {
  150. // {0} is namespace declarations
  151. // {1} is namespace of interface
  152. // {2} is name of interface
  153. // {3} is to be filled with methods and properties etc..
  154. var baseInterfaceString = new StringBuilder();
  155. foreach (var baseInterface in WrappedType.GetInterfaces().Where(i => Repository.TypeIsWrapped(i)))
  156. {
  157. Repository.MarkUsageOfType(baseInterface);
  158. baseInterfaceString.Append(", ");
  159. baseInterfaceString.Append(Repository.GetTargetFullName(baseInterface));
  160. }
  161. const string fileFmt = @"// Wrapper for {5}
  162. {0}
  163. namespace {1}
  164. {{
  165. public interface {2} : NHtmlUnit.IObjectWrapper{3}
  166. {{
  167. {4}
  168. }}
  169. }}
  170. ";
  171. var namespaceIncludes =
  172. @"// Generated class v" + this.htmlUnitVersion + @", don't modify
  173. using System;
  174. using System.Collections.Generic;
  175. using System.Collections.Specialized;
  176. using System.Linq;
  177. using System.Text;
  178. ";
  179. var body = new StringBuilder();
  180. #warning HACK HACK HACK ON
  181. if (!WrappedType.FullName.StartsWith("org.w3c.dom."))
  182. {
  183. foreach (var wpi in Properties.Where(x => x.Value.GetterMethod != null))
  184. wpi.Value.GeneratePropertyCode(body);
  185. foreach (var wm in Methods)
  186. wm.Value.GenerateMethodCode(body);
  187. }
  188. sb.AppendFormat(fileFmt,
  189. namespaceIncludes,
  190. TargetNamespace,
  191. TargetNameWithoutNamespace,
  192. baseInterfaceString,
  193. body,
  194. WrappedType.FullName);
  195. }
  196. public void GenerateUserClassFile()
  197. {
  198. StringBuilder sb = new StringBuilder();
  199. if (!IsInterface)
  200. {
  201. GeneratePartialClassHeader(sb, true);
  202. GeneratePartialClassFooter(sb);
  203. }
  204. var names = RelativeClassName.Split('.');
  205. var directories = new[] { Repository.TargetDirectory, "NonGenerated" }
  206. .Union(names)
  207. .ToArray();
  208. var fileName = Path.Combine(directories) + ".cs";
  209. Repository.WriteTextToFile(fileName, sb.ToString(), false /* Don't overwrite! */);
  210. }
  211. public bool HasNamespaceEntryForType(Type propertyType)
  212. {
  213. const string namespaceListText =
  214. @"using System;
  215. using System.Collections.Generic;
  216. using System.Collections.Specialized;
  217. using System.Linq;
  218. using System.Text;";
  219. List<string> nameSpaceList = namespaceListText.Split(';')
  220. .Select(s => (s.Trim("\r\n \t;".ToCharArray())))
  221. .Where(s => (s.StartsWith("using ")))
  222. .Select(s => (s.Substring("using ".Length).Trim()))
  223. .Where(s => (!String.IsNullOrWhiteSpace(s)))
  224. .Concat(new[] { TargetNamespace }).ToList();
  225. return nameSpaceList.Contains(propertyType.Namespace);
  226. }
  227. private static bool IsMethodSignatureCastable(MethodInfo castTo, MethodInfo castFrom)
  228. {
  229. var targetParameters = castTo.GetParameters();
  230. var sourceParameters = castFrom.GetParameters();
  231. if (targetParameters.Length != sourceParameters.Length)
  232. return false;
  233. for (var i = 0; i < targetParameters.Length; i++)
  234. {
  235. var tp = targetParameters[i];
  236. var sp = sourceParameters[i];
  237. if (tp.ParameterType != sp.ParameterType)
  238. return false;
  239. }
  240. return castTo.ReturnType.IsAssignableFrom(castFrom.ReturnType);
  241. }
  242. private void GeneratePartialClassFooter(StringBuilder sb)
  243. {
  244. sb.AppendLine(" }");
  245. sb.AppendLine("\r\n\r\n}");
  246. }
  247. private void GeneratePartialClassHeader(StringBuilder sb, bool isUserFile)
  248. {
  249. sb.AppendLine("// Generated class v" + this.htmlUnitVersion
  250. + (isUserFile ? ", can be modified" : ", don't modify"));
  251. sb.AppendLine();
  252. if (!isUserFile)
  253. {
  254. sb.AppendLine("using System;");
  255. sb.AppendLine("using System.Collections.Generic;");
  256. sb.AppendLine("using System.Collections.Specialized;");
  257. sb.AppendLine("using System.Linq;");
  258. sb.AppendLine("using System.Text;");
  259. sb.AppendLine();
  260. }
  261. sb.Append("namespace ");
  262. sb.AppendLine(TargetNamespace);
  263. sb.AppendLine("{");
  264. var interfaceList = new StringBuilder();
  265. if (isUserFile)
  266. sb.AppendLine(" public partial class " + TargetNameWithoutNamespace);
  267. else
  268. {
  269. IEnumerable<Type> wrappedInterfaces = WrappedType
  270. .GetInterfaces()
  271. .Where(i => Repository.TypeIsWrapped(i));
  272. foreach (var wrappedInterface in wrappedInterfaces)
  273. {
  274. interfaceList.AppendFormat(", {0}", Repository.GetTargetFullName(wrappedInterface));
  275. Repository.MarkUsageOfType(wrappedInterface);
  276. }
  277. if (WrappedBase != null && Repository.TypeIsWrapped(WrappedBase))
  278. Repository.MarkUsageOfType(WrappedBase);
  279. sb.AppendFormat(
  280. " public partial class {0} : {1}{2}\r\n",
  281. TargetNameWithoutNamespace,
  282. TargetBaseName,
  283. interfaceList
  284. );
  285. }
  286. sb.AppendLine(" {");
  287. }
  288. private WrapperPropInfo GetWrapperPropInfoFor(Dictionary<string, WrapperPropInfo> dict, string propName)
  289. {
  290. WrapperPropInfo wpi;
  291. if (!dict.TryGetValue(propName, out wpi))
  292. {
  293. wpi = new WrapperPropInfo(this, propName, Repository);
  294. dict[propName] = wpi;
  295. }
  296. return wpi;
  297. }
  298. private void ScanMembers(Type type)
  299. {
  300. // The special meal of today: find interfaces that is new on this class, and clashes with existing fields!
  301. var newInterfacesForThisSubclass =
  302. WrappedType
  303. .GetInterfaces()
  304. .Except(WrappedBase != null ? WrappedBase.GetInterfaces() : new Type[] { })
  305. .Where(i => Repository.TypeIsWrapped(i));
  306. var existingNames = new HashSet<string>(
  307. Methods.Values.Select(m => m.TargetMethodInfo.Name));
  308. var methodsOfNewInterfaces =
  309. newInterfacesForThisSubclass
  310. .SelectMany(il => il.GetMethods())
  311. .Where(m => existingNames.Contains(m.Name))
  312. .ToArray();
  313. foreach (var wmiRo in Methods.Values)
  314. {
  315. var wmi = wmiRo; // To get correct lambda closure!
  316. var conflictingMethods = methodsOfNewInterfaces
  317. .Where(m => wmi.TargetMethodInfo.Name == m.Name)
  318. .Where(m => IsMethodSignatureCastable(m, wmi.TargetMethodInfo))
  319. .ToArray();
  320. foreach (var cm in conflictingMethods)
  321. Console.WriteLine("Conflicting method: " + cm);
  322. if (conflictingMethods.Any())
  323. throw new InvalidOperationException();
  324. }
  325. // First extract all properties and methods
  326. IEnumerable<MethodInfo> nonObsoleteMethods = type
  327. .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
  328. .Where(mi => (!mi.GetCustomAttributes(typeof(ObsoleteAttribute), true).Any()))
  329. .Where(mi => (!mi.Name.Contains("<")))
  330. // TODO: Support methods with weird names that starts with "<bridge>"
  331. .Where(mi =>
  332. (type.BaseType == null
  333. || type.BaseType.GetMethod(mi.Name,
  334. mi.GetParameters().Select(pi => pi.ParameterType).ToArray())
  335. == null));
  336. foreach (MethodInfo method in nonObsoleteMethods)
  337. {
  338. bool wrappedAsProperty = false;
  339. if (method.Name.Length > 3 && method.IsPublic)
  340. {
  341. if (method.Name.StartsWith("get")
  342. && method.ReturnType != typeof(void)
  343. && method.GetParameters().Length == 0
  344. // We can't convert methods to properties that will cause the property to have the same name as the type.
  345. && method.Name.Substring(3) != type.Name)
  346. {
  347. GetWrapperPropInfoFor(Properties, method.Name.Substring(3)).GetterMethod = method;
  348. wrappedAsProperty = true;
  349. }
  350. else if (method.Name.StartsWith("set")
  351. && method.GetParameters().Length == 1
  352. && method.ReturnType == typeof(void))
  353. {
  354. GetWrapperPropInfoFor(Properties, method.Name.Substring(3)).SetterMethod = method;
  355. wrappedAsProperty = true;
  356. }
  357. }
  358. if (!wrappedAsProperty)
  359. Methods.Add(method.ToString(), new WrapperMethodInfo(this, method));
  360. }
  361. if (!type.IsAbstract)
  362. {
  363. // Extract constructors
  364. var nonObsoletePublicConstructors = type
  365. .GetConstructors()
  366. .Where(mi => (!mi.GetCustomAttributes(typeof(ObsoleteAttribute), true).Any()))
  367. .Where(mi => mi.DeclaringType == type)
  368. .Where(mi => !mi.IsAbstract);
  369. foreach (var constructor in nonObsoletePublicConstructors)
  370. Constructors.Add(new WrapperConstructorInfo(this, constructor));
  371. }
  372. // Extract public static
  373. var publicStaticFields = type
  374. .GetFields(BindingFlags.Public | BindingFlags.Static)
  375. .Where(mi => (!mi.GetCustomAttributes(typeof(ObsoleteAttribute), true).Any()))
  376. .Where(f => !f.FieldType.IsInterface);
  377. foreach (var field in publicStaticFields)
  378. StaticPublicFields.Add(new WrapperStaticPublicField(this, field));
  379. }
  380. private bool ShouldMapAsProperty(KeyValuePair<string, WrapperPropInfo> x)
  381. {
  382. if (x.Value.GetterMethod == null)
  383. {
  384. //TODO: Consider this. When method identified as property has no GetterMethod, the SetterMethod is neither mapped as property or method. [TM]
  385. //if (!Methods.ContainsKey(x.Value.SetterMethod.ToString()))
  386. // Methods.Add(x.Value.SetterMethod.ToString(), new WrapperMethodInfo(this, x.Value.SetterMethod));
  387. return false;
  388. }
  389. if (x.Value.SetterMethod != null
  390. && x.Value.GetterMethod.ReturnType != x.Value.SetterMethod.GetParameters().First().ParameterType)
  391. {
  392. Methods.Add(x.Value.GetterMethod.ToString(), new WrapperMethodInfo(this, x.Value.GetterMethod));
  393. Methods.Add(x.Value.SetterMethod.ToString(), new WrapperMethodInfo(this, x.Value.SetterMethod));
  394. return false;
  395. }
  396. return true;
  397. }
  398. }
  399. }