2
// NSObjectInfoService.cs
5
// Michael Hutchinson <mhutchinson@novell.com>
7
// Copyright (c) 2011 Novell, Inc.
8
// Copyright (c) 2011 Xamarin Inc.
10
// Permission is hereby granted, free of charge, to any person obtaining a copy
11
// of this software and associated documentation files (the "Software"), to deal
12
// in the Software without restriction, including without limitation the rights
13
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
// copies of the Software, and to permit persons to whom the Software is
15
// furnished to do so, subject to the following conditions:
17
// The above copyright notice and this permission notice shall be included in
18
// all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
using System.Collections.Generic;
32
using System.Text.RegularExpressions;
34
using MonoDevelop.Projects;
35
using ICSharpCode.NRefactory.TypeSystem;
36
using ICSharpCode.NRefactory.TypeSystem.Implementation;
37
using MonoDevelop.Ide.TypeSystem;
39
using MonoDevelop.Ide;
40
using MonoDevelop.Core;
42
namespace MonoDevelop.MacDev.ObjCIntegration
44
public class NSObjectInfoService
46
//static readonly Regex frameworkRegex = new Regex ("#import\\s+<([A-Z][A-Za-z]*)/([A-Z][A-Za-z]*)\\.h>", RegexOptions.Compiled);
47
static readonly Regex typeInfoRegex = new Regex ("@(interface|protocol)\\s+(\\w*)\\s*:\\s*(\\w*)", RegexOptions.Compiled);
48
static readonly Regex ibRegex = new Regex ("(-\\s*\\(IBAction\\)|IBOutlet)\\s*([^;]*);", RegexOptions.Compiled);
49
static readonly char[] colonChar = { ':' };
50
static readonly char[] whitespaceChars = { ' ', '\t', '\n', '\r' };
51
static readonly char[] splitActionParamsChars = { ' ', '\t', '\n', '\r', '*', '(', ')' };
53
readonly ITypeReference nsobjectType, registerAttType, connectAttType, exportAttType, modelAttType,
54
iboutletAttType, ibactionAttType;
56
static Dictionary<TypeSystemService.ProjectContentWrapper,NSObjectProjectInfo> infos = new Dictionary<TypeSystemService.ProjectContentWrapper, NSObjectProjectInfo> ();
58
ITypeDefinition Resolve (TypeSystemService.ProjectContentWrapper dom, ITypeReference reference)
60
return reference.Resolve (dom.Compilation).GetDefinition ();
64
static NSObjectInfoService ()
66
TypeSystemService.ProjectUnloaded += HandleDomUnloaded;
69
public NSObjectInfoService (string wrapperRoot)
71
this.WrapperRoot = wrapperRoot;
72
var typeNamespace = wrapperRoot + ".Foundation";
73
connectAttType = new GetClassTypeReference (typeNamespace, "ConnectAttribute");
74
exportAttType = new GetClassTypeReference (typeNamespace, "ExportAttribute");
75
iboutletAttType = new GetClassTypeReference (typeNamespace, "OutletAttribute");
76
ibactionAttType = new GetClassTypeReference (typeNamespace, "ActionAttribute");
77
registerAttType = new GetClassTypeReference (typeNamespace, "RegisterAttribute");
78
modelAttType = new GetClassTypeReference (typeNamespace, "ModelAttribute");
79
nsobjectType = new GetClassTypeReference (typeNamespace, "NSObject");
82
public string WrapperRoot { get; private set; }
84
public NSObjectProjectInfo GetProjectInfo (DotNetProject project, IAssembly lookinAssembly = null)
86
var dom = TypeSystemService.GetProjectContentWrapper (project);
87
project.ReferenceAddedToProject += HandleDomReferencesUpdated;
88
project.ReferenceRemovedFromProject += HandleDomReferencesUpdated;
89
return GetProjectInfo (dom, lookinAssembly);
92
public NSObjectProjectInfo GetProjectInfo (TypeSystemService.ProjectContentWrapper dom, IAssembly lookinAssembly = null)
94
NSObjectProjectInfo info;
97
if (infos.TryGetValue (dom, out info))
100
var nso = Resolve (dom, nsobjectType);
101
//only include DOMs that can resolve NSObject
102
if (nso == null || nso.Kind == TypeKind.Unknown)
105
info = new NSObjectProjectInfo (dom, this, lookinAssembly);
111
static void HandleDomReferencesUpdated (object sender, ProjectReferenceEventArgs e)
113
var project = (DotNetProject)sender;
114
var dom = TypeSystemService.GetProjectContentWrapper (project);
117
NSObjectProjectInfo info;
119
if (!infos.TryGetValue (dom, out info))
122
info.SetNeedsUpdating ();
125
static void HandleDomUnloaded (object sender, ProjectUnloadEventArgs e)
127
var project = e.Project as DotNetProject;
134
project.ReferenceAddedToProject -= HandleDomReferencesUpdated;
135
project.ReferenceRemovedFromProject -= HandleDomReferencesUpdated;
140
internal IEnumerable<NSObjectTypeInfo> GetRegisteredObjects (TypeSystemService.ProjectContentWrapper dom, IAssembly assembly)
142
var nso = Resolve (dom, nsobjectType);
143
if (nso == null || nso.Kind == TypeKind.Unknown)
144
throw new Exception ("Could not get NSObject from type database");
146
//FIXME: only emit this for the wrapper NS
147
// yield return new NSObjectTypeInfo ("NSObject", nso.GetDefinition ().FullName, null, null, false, false, false);
148
int cnt = 0, infcnt=0, models=0;
149
nso = assembly.Compilation.Import (nso);
150
foreach (var contextType in assembly.GetAllTypeDefinitions ()) {
151
if (contextType.IsDerivedFrom (nso)) {
152
var info = ConvertType (dom, contextType);
159
NSObjectTypeInfo ConvertType (TypeSystemService.ProjectContentWrapper dom, ITypeDefinition type)
161
string objcName = null;
162
bool isModel = false;
163
bool registeredInDesigner = true;
165
foreach (var att in type.Attributes) {
166
var attType = att.AttributeType;
167
if (attType.Equals (Resolve (dom, registerAttType))) {
168
if (type.GetProjectContent () != null) {
169
registeredInDesigner &=
170
MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (att.Region.FileName);
173
//type registered with an explicit type name are up to the user to provide a valid name
174
var posArgs = att.PositionalArguments;
175
if (posArgs.Count == 1 || posArgs.Count == 2)
176
objcName = posArgs [0].ConstantValue as string;
177
//non-nested types in the root namespace have names accessible from obj-c
178
else if (string.IsNullOrEmpty (type.Namespace) && type.Name.IndexOf ('.') < 0)
179
objcName = type.Name;
182
if (attType.Equals (Resolve (dom, modelAttType)))
186
if (string.IsNullOrEmpty (objcName))
189
string baseType = type.DirectBaseTypes.First ().ReflectionName;
190
if (baseType == "System.Object")
193
bool isUserType = !type.ParentAssembly.Equals (Resolve (dom, nsobjectType).ParentAssembly);
195
var info = new NSObjectTypeInfo (objcName, type.ReflectionName, null, baseType, isModel, isUserType, registeredInDesigner);
197
if (info.IsUserType) {
198
UpdateTypeMembers (dom, info, type);
199
info.DefinedIn = type.Parts.Select (p => (string) p.Region.FileName).ToArray ();
205
void UpdateTypeMembers (TypeSystemService.ProjectContentWrapper dom, NSObjectTypeInfo info, ITypeDefinition type)
207
info.Actions.Clear ();
208
info.Outlets.Clear ();
209
foreach (var prop in type.Properties) {
210
foreach (var att in prop.Attributes) {
211
var attType = att.AttributeType;
212
bool isIBOutlet = attType.Equals (Resolve (dom, iboutletAttType));
214
if (!attType.Equals (Resolve (dom, connectAttType)))
218
var posArgs = att.PositionalArguments;
219
if (posArgs.Count == 1)
220
name = posArgs [0].ConstantValue as string;
221
if (string.IsNullOrEmpty (name))
224
// HACK: Work around bug #1586 in the least obtrusive way possible. Strip out any outlet
225
// with the name 'view' on subclasses of MonoTouch.UIKit.UIViewController to avoid
226
// conflicts with the view property mapped there
227
if (name == "view") {
228
if (type.GetAllBaseTypeDefinitions ().Any (p => p.ReflectionName == "MonoTouch.UIKit.UIViewController"))
232
var ol = new IBOutlet (name, prop.Name, null, prop.ReturnType.ReflectionName);
233
if (MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (prop.Region.FileName))
234
ol.IsDesigner = true;
235
info.Outlets.Add (ol);
239
foreach (var meth in type.Methods) {
240
foreach (var att in meth.Attributes) {
241
var attType = att.AttributeType;
242
bool isIBAction = attType.Equals (Resolve (dom, ibactionAttType));
244
if (!attType.Equals (Resolve (dom, exportAttType)))
248
bool isDesigner = meth.Parts.Any (part => MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (part.Region.FileName));
249
//only support Export from old designer files, user code must be IBAction
250
if (!isDesigner && !isIBAction)
253
string[] name = null;
254
var posArgs = att.PositionalArguments;
255
if (posArgs.Count == 1 || posArgs.Count == 2) {
256
var n = posArgs [0].ConstantValue as string;
257
if (!string.IsNullOrEmpty (n))
258
name = n.Split (colonChar);
260
var action = new IBAction (name != null ? name [0] : meth.Name, meth.Name);
262
foreach (var param in meth.Parameters) {
263
string label = name != null && i < name.Length ? name [i] : null;
264
if (label != null && label.Length == 0)
266
action.Parameters.Add (new IBActionParameter (label, param.Name, null, param.Type.ReflectionName));
268
if (meth.Parts.Any (part => MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (part.Region.FileName)))
269
action.IsDesigner = true;
270
info.Actions.Add (action);
276
public static NSObjectTypeInfo ParseHeader (string headerFile)
278
string text = File.ReadAllText (headerFile);
279
string userType = null, userBaseType = null;
280
MatchCollection matches;
281
NSObjectTypeInfo type;
283
// First, grep for classes
284
matches = typeInfoRegex.Matches (text);
285
foreach (Match match in matches) {
286
if (match.Groups[1].Value != "interface")
289
if (userType != null) {
290
// UNSUPPORTED: more than 1 user-type defined in this header
294
userType = match.Groups[2].Value;
295
userBaseType = match.Groups[3].Value;
298
if (userType == null)
301
type = new NSObjectTypeInfo (userType, null, userBaseType, null, false, true, true);
303
// Now grep for IBActions and IBOutlets
304
matches = ibRegex.Matches (text);
305
foreach (Match match in matches) {
306
var kind = match.Groups[1].Value;
307
var def = match.Groups[2].Value;
308
if (kind == "IBOutlet") {
309
var split = def.Split (whitespaceChars, StringSplitOptions.RemoveEmptyEntries);
310
string objcType = split[0].TrimEnd ('*');
311
string objcName = null;
313
for (int i = 1; i < split.Length; i++) {
314
objcName = split[i].TrimStart ('*');
315
if (string.IsNullOrEmpty (objcName))
318
if (i + 1 < split.Length) {
319
// This is a bad sign... what tokens are after the name??
325
if (string.IsNullOrEmpty (objcType) || string.IsNullOrEmpty (objcName)) {
326
MessageService.ShowError (GettextCatalog.GetString ("Error while parsing header file."),
327
string.Format (GettextCatalog.GetString ("The definition '{0}' can't be parsed."), def));
329
// We can't recover if objcName is empty...
330
if (string.IsNullOrEmpty (objcName))
333
// We can try using NSObject...
334
objcType = "NSObject";
337
if (objcType == "id")
338
objcType = "NSObject";
340
IBOutlet outlet = new IBOutlet (objcName, objcName, objcType, null);
341
outlet.IsDesigner = true;
343
type.Outlets.Add (outlet);
345
string[] split = def.Split (colonChar);
346
string name = split[0].Trim ();
347
var action = new IBAction (name, name);
348
action.IsDesigner = true;
351
for (int i = 1; i < split.Length; i++) {
352
var s = split[i].Split (splitActionParamsChars, StringSplitOptions.RemoveEmptyEntries);
353
string objcType = s[0];
354
if (objcType == "id")
355
objcType = "NSObject";
356
var par = new IBActionParameter (label, s[1], objcType, null);
357
label = s.Length == 3? s[2] : null;
358
action.Parameters.Add (par);
361
type.Actions.Add (action);
369
public class UserTypeChangeEventArgs : EventArgs
371
public UserTypeChangeEventArgs (IList<UserTypeChange> changes)
373
this.Changes = changes;
376
public IList<UserTypeChange> Changes { get; private set; }
379
public class UserTypeChange
381
public UserTypeChange (NSObjectTypeInfo type, UserTypeChangeKind kind)
387
public NSObjectTypeInfo Type { get; private set; }
388
public UserTypeChangeKind Kind { get; private set; }
391
public enum UserTypeChangeKind