PageRenderTime 199ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

/main/src/addins/MonoDevelop.MacDev/ObjCIntegration/NSObjectInfoService.cs

https://github.com/nmf/monodevelop
C# | 308 lines | 244 code | 33 blank | 31 comment | 63 complexity | a05733b7376001de1fc65bf9fb76ad1d MD5 | raw file
  1. //
  2. // NSObjectInfoService.cs
  3. //
  4. // Author:
  5. // Michael Hutchinson <mhutchinson@novell.com>
  6. //
  7. // Copyright (c) 2011 Novell, Inc.
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. using System;
  27. using System.Linq;
  28. using System.Collections.Generic;
  29. using System.Text.RegularExpressions;
  30. using MonoDevelop.Projects;
  31. using MonoDevelop.Projects.Dom;
  32. using MonoDevelop.Projects.Dom.Parser;
  33. using MonoDevelop.Ide;
  34. using MonoDevelop.Core;
  35. namespace MonoDevelop.MacDev.ObjCIntegration
  36. {
  37. public class NSObjectInfoService
  38. {
  39. static readonly Regex ibRegex = new Regex ("(- \\(IBAction\\)|IBOutlet)([^;]*);", RegexOptions.Compiled);
  40. static readonly char[] colonChar = { ':' };
  41. static readonly char[] whitespaceChars = { ' ', '\t', '\n', '\r' };
  42. static readonly char[] splitActionParamsChars = { ' ', '\t', '\n', '\r', '*', '(', ')' };
  43. readonly IReturnType nsobjectType, registerAttType, connectAttType, exportAttType, modelAttType,
  44. iboutletAttType, ibactionAttType;
  45. static Dictionary<ProjectDom,NSObjectProjectInfo> infos = new Dictionary<ProjectDom, NSObjectProjectInfo> ();
  46. public NSObjectInfoService (string wrapperRoot)
  47. {
  48. this.WrapperRoot = wrapperRoot;
  49. string foundation = wrapperRoot + ".Foundation";
  50. connectAttType = new DomReturnType (foundation, "ConnectAttribute");
  51. exportAttType = new DomReturnType (foundation, "ExportAttribute");
  52. iboutletAttType = new DomReturnType (foundation, "OutletAttribute");
  53. ibactionAttType = new DomReturnType (foundation, "ActionAttribute");
  54. registerAttType = new DomReturnType (foundation, "RegisterAttribute");
  55. modelAttType = new DomReturnType (foundation, "ModelAttribute");
  56. nsobjectType = new DomReturnType (foundation, "NSObject");
  57. }
  58. public string WrapperRoot { get; private set; }
  59. public NSObjectProjectInfo GetProjectInfo (DotNetProject project)
  60. {
  61. var dom = ProjectDomService.GetProjectDom (project);
  62. if (dom == null)
  63. return null;
  64. dom.ForceUpdate (true);
  65. return GetProjectInfo (dom);
  66. }
  67. public NSObjectProjectInfo GetProjectInfo (ProjectDom dom)
  68. {
  69. NSObjectProjectInfo info;
  70. lock (infos) {
  71. if (infos.TryGetValue (dom, out info))
  72. return info;
  73. //only include DOMs that can resolve NSObject
  74. var nso = dom.GetType (nsobjectType);
  75. if (nso == null) {
  76. infos[dom] = null;
  77. return null;
  78. }
  79. info = new NSObjectProjectInfo (dom, this);
  80. infos[dom] = info;
  81. dom.Unloaded += HandleDomUnloaded;
  82. dom.ReferencesUpdated += HandleDomReferencesUpdated;
  83. }
  84. return info;
  85. }
  86. static void HandleDomReferencesUpdated (object sender, EventArgs e)
  87. {
  88. var dom = (ProjectDom)sender;
  89. NSObjectProjectInfo info;
  90. lock (infos) {
  91. if (!infos.TryGetValue (dom, out info))
  92. return;
  93. }
  94. info.SetNeedsUpdating ();
  95. }
  96. static void HandleDomUnloaded (object sender, EventArgs e)
  97. {
  98. var dom = (ProjectDom)sender;
  99. lock (infos) {
  100. dom.Unloaded -= HandleDomUnloaded;
  101. dom.ReferencesUpdated -= HandleDomReferencesUpdated;
  102. infos.Remove (dom);
  103. }
  104. }
  105. internal IEnumerable<NSObjectTypeInfo> GetRegisteredObjects (ProjectDom dom)
  106. {
  107. var nso = dom.GetType (nsobjectType);
  108. if (nso == null)
  109. throw new Exception ("Could not get NSObject from type database");
  110. //FIXME: only emit this for the wrapper NS
  111. yield return new NSObjectTypeInfo ("NSObject", nsobjectType.FullName, null, null, false);
  112. foreach (var type in dom.GetSubclasses (nso, false)) {
  113. var info = ConvertType (dom, type);
  114. if (info != null)
  115. yield return info;
  116. }
  117. }
  118. NSObjectTypeInfo ConvertType (ProjectDom dom, IType type)
  119. {
  120. string objcName = null;
  121. bool isModel = false;
  122. bool registeredInDesigner = true;
  123. foreach (var part in type.Parts) {
  124. foreach (var att in part.Attributes) {
  125. if (att.AttributeType.FullName == registerAttType.FullName) {
  126. if (type.SourceProject != null) {
  127. registeredInDesigner &=
  128. MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (part.CompilationUnit.FileName);
  129. }
  130. //type registered with an explicit type name are up to the user to provide a valid name
  131. // Note that the attribute now takes one *or* two parameters.
  132. if (att.PositionalArguments.Count == 1 || att.PositionalArguments.Count == 2)
  133. objcName = (string)((System.CodeDom.CodePrimitiveExpression)att.PositionalArguments[0]).Value;
  134. //non-nested types in the root namespace have names accessible from obj-c
  135. else if (string.IsNullOrEmpty (type.Namespace) && type.Name.IndexOf ('.') < 0)
  136. objcName = type.Name;
  137. }
  138. if (att.AttributeType.FullName == modelAttType.FullName) {
  139. isModel = true;
  140. }
  141. }
  142. }
  143. if (string.IsNullOrEmpty (objcName))
  144. return null;
  145. var info = new NSObjectTypeInfo (objcName, type.FullName, null, type.BaseType.FullName, isModel);
  146. info.IsUserType = type.SourceProject != null;
  147. info.IsRegisteredInDesigner = registeredInDesigner;
  148. if (info.IsUserType) {
  149. UpdateTypeMembers (dom, info, type);
  150. info.DefinedIn = type.Parts.Select (p => (string) p.CompilationUnit.FileName).ToArray ();
  151. }
  152. return info;
  153. }
  154. void UpdateTypeMembers (ProjectDom dom, NSObjectTypeInfo info, IType type)
  155. {
  156. info.Actions.Clear ();
  157. info.Outlets.Clear ();
  158. foreach (var prop in type.Properties) {
  159. foreach (var att in prop.Attributes) {
  160. bool isIBOutlet = att.AttributeType.FullName == iboutletAttType.FullName;
  161. if (!isIBOutlet) {
  162. if (att.AttributeType.FullName != connectAttType.FullName)
  163. continue;
  164. }
  165. string name = null;
  166. if (att.PositionalArguments.Count == 1)
  167. name = (string)((System.CodeDom.CodePrimitiveExpression)att.PositionalArguments[0]).Value;
  168. if (string.IsNullOrEmpty (name))
  169. name = prop.Name;
  170. var ol = new IBOutlet (name, prop.Name, null, prop.ReturnType.FullName);
  171. if (MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (prop.DeclaringType.CompilationUnit.FileName))
  172. ol.IsDesigner = true;
  173. info.Outlets.Add (ol);
  174. break;
  175. }
  176. }
  177. foreach (var meth in type.Methods) {
  178. foreach (var att in meth.Attributes) {
  179. bool isIBAction = att.AttributeType.FullName == ibactionAttType.FullName;
  180. if (!isIBAction) {
  181. if (att.AttributeType.FullName != exportAttType.FullName)
  182. continue;
  183. }
  184. bool isDesigner = MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (
  185. meth.DeclaringType.CompilationUnit.FileName);
  186. //only support Export from old designer files, user code must be IBAction
  187. if (!isDesigner && !isIBAction)
  188. continue;
  189. string[] name = null;
  190. if (att.PositionalArguments.Count == 1) {
  191. var n = (string)((System.CodeDom.CodePrimitiveExpression)att.PositionalArguments[0]).Value;
  192. if (!string.IsNullOrEmpty (n))
  193. name = n.Split (colonChar);
  194. }
  195. var action = new IBAction (name != null? name [0] : meth.Name, meth.Name);
  196. int i = 1;
  197. foreach (var param in meth.Parameters) {
  198. string label = name != null && i < name.Length? name[i] : null;
  199. if (label != null && label.Length == 0)
  200. label = null;
  201. action.Parameters.Add (new IBActionParameter (label, param.Name, null, param.ReturnType.FullName));
  202. }
  203. if (MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (meth.DeclaringType.CompilationUnit.FileName))
  204. action.IsDesigner = true;
  205. info.Actions.Add (action);
  206. break;
  207. }
  208. }
  209. }
  210. public static NSObjectTypeInfo ParseHeader (string headerFile)
  211. {
  212. var text = System.IO.File.ReadAllText (headerFile);
  213. var matches = ibRegex.Matches (text);
  214. var type = new NSObjectTypeInfo (System.IO.Path.GetFileNameWithoutExtension (headerFile), null, null, null, false);
  215. foreach (Match match in matches) {
  216. var kind = match.Groups[1].Value;
  217. var def = match.Groups[2].Value;
  218. if (kind == "IBOutlet") {
  219. var split = def.Split (whitespaceChars, StringSplitOptions.RemoveEmptyEntries);
  220. if (split.Length != 2)
  221. continue;
  222. string objcName = split[1].TrimStart ('*');
  223. string objcType = split[0].TrimEnd ('*');
  224. if (objcType == "id")
  225. objcType = "NSObject";
  226. if (string.IsNullOrEmpty (objcType)) {
  227. MessageService.ShowError (GettextCatalog.GetString ("Error while parsing header file."),
  228. string.Format (GettextCatalog.GetString ("The definition '{0}' can't be parsed."), def));
  229. objcType = "NSObject";
  230. }
  231. type.Outlets.Add (new IBOutlet (objcName, null, objcType, null));
  232. } else {
  233. string[] split = def.Split (colonChar);
  234. var action = new IBAction (split[0].Trim (), null);
  235. string label = null;
  236. for (int i = 1; i < split.Length; i++) {
  237. var s = split[i].Split (splitActionParamsChars, StringSplitOptions.RemoveEmptyEntries);
  238. string objcType = s[0];
  239. if (objcType == "id")
  240. objcType = "NSObject";
  241. var par = new IBActionParameter (label, s[1], objcType, null);
  242. label = s.Length == 3? s[2] : null;
  243. action.Parameters.Add (par);
  244. }
  245. type.Actions.Add (action);
  246. }
  247. }
  248. return type;
  249. }
  250. }
  251. public class UserTypeChangeEventArgs : EventArgs
  252. {
  253. public UserTypeChangeEventArgs (IList<UserTypeChange> changes)
  254. {
  255. this.Changes = changes;
  256. }
  257. public IList<UserTypeChange> Changes { get; private set; }
  258. }
  259. public class UserTypeChange
  260. {
  261. public UserTypeChange (NSObjectTypeInfo type, UserTypeChangeKind kind)
  262. {
  263. this.Type = type;
  264. this.Kind = kind;
  265. }
  266. public NSObjectTypeInfo Type { get; private set; }
  267. public UserTypeChangeKind Kind { get; private set; }
  268. }
  269. public enum UserTypeChangeKind
  270. {
  271. Added,
  272. Removed,
  273. Modified
  274. }
  275. }