5
// Michael Hutchinson <mhutchinson@novell.com>
7
// Copyright (c) 2011 Novell, Inc.
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:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
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
28
using System.Collections.Generic;
31
using System.CodeDom.Compiler;
33
using MonoDevelop.Core;
35
namespace MonoDevelop.MacDev.ObjCIntegration
37
public class NSObjectTypeInfo
39
public NSObjectTypeInfo (string objcName, string cliName, string baseObjCName, string baseCliName, bool isModel, bool isUserType, bool isRegisteredInDesigner)
41
IsRegisteredInDesigner = isRegisteredInDesigner;
42
BaseObjCType = baseObjCName;
43
BaseCliType = baseCliName;
44
IsUserType = isUserType;
49
UserTypeReferences = new HashSet<string> ();
50
Outlets = new List<IBOutlet> ();
51
Actions = new List<IBAction> ();
54
public string ObjCName { get; private set; }
55
public string CliName { get; internal set; }
56
public bool IsModel { get; internal set; }
58
public string BaseObjCType { get; internal set; }
59
public string BaseCliType { get; internal set; }
60
public bool BaseIsModel { get; internal set; }
62
public bool IsUserType { get; internal set; }
63
public bool IsRegisteredInDesigner { get; internal set; }
65
public List<IBOutlet> Outlets { get; private set; }
66
public List<IBAction> Actions { get; private set; }
68
public string[] DefinedIn { get; internal set; }
70
public string GetDesignerFile ()
72
if (DefinedIn != null)
73
foreach (var d in DefinedIn)
74
if (MonoDevelop.DesignerSupport.CodeBehind.IsDesignerFile (d))
79
public HashSet<string> UserTypeReferences { get; private set; }
81
static void AddNamespaceForCliType (HashSet<string> namespaces, string ignore, string typeName)
89
if ((dot = typeName.LastIndexOf ('.')) == -1)
92
ns = typeName.Substring (0, dot);
93
if (ns != ignore && !namespaces.Contains (ns))
97
public HashSet<string> GetNamespaces ()
99
HashSet<string> namespaces = new HashSet<string> ();
100
string ignore = null;
103
if ((dot = CliName.LastIndexOf ('.')) != -1)
104
ignore = CliName.Substring (0, dot);
106
AddNamespaceForCliType (namespaces, ignore, BaseCliType);
108
foreach (var outlet in Outlets)
109
AddNamespaceForCliType (namespaces, ignore, outlet.CliType);
111
foreach (var action in Actions) {
112
foreach (var param in action.Parameters)
113
AddNamespaceForCliType (namespaces, ignore, param.CliType);
119
static string GetSuggestedRegisterName (string fullName)
121
int dot = fullName.LastIndexOf ('.');
125
return fullName.Substring (dot + 1);
128
public void GenerateObjcType (string directory, string[] frameworks)
131
// We don't generate header files for protocols.
135
string hFilePath = Path.Combine (directory, ObjCName + ".h");
136
string mFilePath = Path.Combine (directory, ObjCName + ".m");
138
using (var sw = File.CreateText (hFilePath)) {
139
sw.WriteLine (modificationWarning);
142
foreach (var framework in frameworks)
143
sw.WriteLine ("#import <{0}/{0}.h>", framework);
147
foreach (var reference in UserTypeReferences)
148
sw.WriteLine ("#import \"{0}.h\"", reference);
152
if (BaseObjCType == null && BaseCliType != null && !BaseIsModel) {
153
throw new ObjectiveCGenerationException (string.Format (
154
"Could not generate class '{0}' as its base type '{1}' could not be resolved to Objective-C.\n\n" +
155
"Hint: Try adding [Register (\"{2}\")] to the class definition for {1}.",
156
CliName, BaseCliType, GetSuggestedRegisterName (BaseCliType)), this);
159
var baseType = BaseIsModel ? "NSObject" : BaseObjCType;
160
sw.WriteLine ("@interface {0} : {1} {{", ObjCName, baseType);
161
foreach (var outlet in Outlets) {
162
sw.WriteLine ("\t{0} *_{1};", AsId (outlet.ObjCType), outlet.ObjCName);
167
foreach (var outlet in Outlets) {
168
var type = AsId (outlet.ObjCType);
169
if (string.IsNullOrEmpty (type)) {
170
throw new ObjectiveCGenerationException (string.Format (
171
"Could not generate outlet '{0}' in class '{1}' as its type '{2}' could not be resolved to Objective-C.\n\n" +
172
"Hint: Try adding [Register (\"{3}\")] to the class definition for {2}.",
173
outlet.CliName, this.CliName, outlet.CliType, GetSuggestedRegisterName (outlet.CliType)), this);
175
sw.WriteLine ("@property (nonatomic, retain) IBOutlet {0} *{1};", type, outlet.ObjCName);
179
foreach (var action in Actions) {
180
WriteActionSignature (action, sw);
185
sw.WriteLine ("@end");
188
using (var sw = File.CreateText (mFilePath)) {
189
sw.WriteLine (modificationWarning);
192
sw.WriteLine ("#import \"{0}.h\"", ObjCName);
195
sw.WriteLine ("@implementation {0}", ObjCName);
198
bool hasOutlet = false;
199
foreach (var outlet in Outlets) {
200
sw.WriteLine ("@synthesize {0} = _{0};", outlet.ObjCName);
206
foreach (var action in Actions) {
207
if (action.Parameters.Any (p => p.ObjCType == null))
209
WriteActionSignature (action, sw);
215
sw.WriteLine ("@end");
218
var lastSourceUpdateTime = DefinedIn.Max (f => File.GetLastWriteTime (f));
219
File.SetLastWriteTime (hFilePath, lastSourceUpdateTime);
220
File.SetLastWriteTime (mFilePath, lastSourceUpdateTime);
223
static string AsId (string objcType)
225
if (objcType == "NSObject")
230
static string modificationWarning =
232
"// This file has been generated automatically by MonoDevelop to\n" +
233
"// mirror C# types. Changes in this file made by drag-connecting\n" +
234
"// from the UI designer will be synchronized back to C#, but\n" +
235
"// more complex manual changes may not transfer correctly.\n";
237
void WriteActionSignature (IBAction action, System.IO.TextWriter writer)
239
writer.Write ("- (IBAction){0}", action.ObjCName);
242
foreach (var param in action.Parameters) {
243
string paramType = param.ObjCType;
244
if (paramType == null) {
245
throw new ObjectiveCGenerationException (string.Format (
246
"Could not generate Obj-C code for action '{0}' in class '{1}' as the type '{2}'" +
247
"of its parameter '{3}' could not be resolved to Obj-C",
248
action.CliName, this.CliName, param.CliType, param.Name), this);
251
if (isFirst && paramType == "NSObject")
254
paramType = paramType + " *";
258
writer.Write (":({0}){1}", paramType, param.Name);
260
writer.Write (" {0}:({1}){2}", param.Label, paramType, param.Name);
266
/// Merges CLI info from previous version of the type into the parsed objc-type.
268
public void MergeCliInfo (NSObjectTypeInfo previousType)
270
CliName = previousType.CliName;
271
DefinedIn = previousType.DefinedIn;
272
IsModel = previousType.IsModel;
273
BaseIsModel = previousType.BaseIsModel;
274
IsUserType = previousType.IsUserType;
275
IsRegisteredInDesigner = previousType.IsRegisteredInDesigner;
277
var existingOutlets = new Dictionary<string,IBOutlet> ();
278
foreach (var o in previousType.Outlets)
279
existingOutlets[o.ObjCName] = o;
281
var existingActions = new Dictionary<string,IBAction> ();
282
foreach (var a in previousType.Actions)
283
existingActions[a.ObjCName] = a;
285
foreach (var a in Actions) {
287
if (existingActions.TryGetValue (a.ObjCName, out existing)) {
288
a.IsDesigner = existing.IsDesigner;
289
a.CliName = existing.CliName;
293
foreach (var o in Outlets) {
295
if (existingOutlets.TryGetValue (o.ObjCName, out existing)) {
296
o.IsDesigner = existing.IsDesigner;
297
o.CliName = existing.CliName;
302
public void GenerateCodeTypeDeclaration (CodeDomProvider provider, CodeGeneratorOptions generatorOptions,
303
string wrapperName, out CodeTypeDeclaration ctd, out string ns)
305
var registerAtt = new CodeTypeReference (wrapperName + ".Foundation.RegisterAttribute");
307
ctd = new System.CodeDom.CodeTypeDeclaration () {
311
if (Outlets.Any (o => o.IsDesigner) || Actions.Any (a => a.IsDesigner))
312
AddWarningDisablePragmas (ctd, provider);
314
var dotIdx = CliName.LastIndexOf ('.');
316
ns = CliName.Substring (0, dotIdx);
317
ctd.Name = CliName.Substring (dotIdx + 1);
322
if (IsRegisteredInDesigner)
323
AddAttribute (ctd.CustomAttributes, registerAtt, ObjCName);
325
GenerateActionsOutlets (provider, ctd, wrapperName);
328
void GenerateActionsOutlets (CodeDomProvider provider, CodeTypeDeclaration type, string wrapperName)
330
var outletAtt = new CodeTypeReference (wrapperName + ".Foundation.OutletAttribute");
331
var actionAtt = new CodeTypeReference (wrapperName + ".Foundation.ActionAttribute");
333
foreach (var a in Actions)
335
GenerateAction (actionAtt, type, a, provider);
337
foreach (var o in Outlets)
339
AddOutletProperty (outletAtt, type, o.CliName, new CodeTypeReference (o.CliType));
342
static void AddOutletProperty (CodeTypeReference outletAtt, CodeTypeDeclaration type, string name,
343
CodeTypeReference typeRef)
345
var fieldName = "__impl_" + name;
346
var field = new CodeMemberField (typeRef, fieldName);
348
var prop = new CodeMemberProperty () {
352
AddAttribute (prop.CustomAttributes, outletAtt, name);
354
var setValue = new CodePropertySetValueReferenceExpression ();
355
var thisRef = new CodeThisReferenceExpression ();
356
var fieldRef = new CodeFieldReferenceExpression (thisRef, fieldName);
358
prop.SetStatements.Add (new CodeAssignStatement (fieldRef, setValue));
359
prop.GetStatements.Add (new CodeMethodReturnStatement (fieldRef));
361
prop.Attributes = field.Attributes = (prop.Attributes & ~MemberAttributes.AccessMask) | MemberAttributes.Private;
363
type.Members.Add (prop);
364
type.Members.Add (field);
367
static void AddAttribute (CodeAttributeDeclarationCollection atts, CodeTypeReference type, string val)
369
atts.Add (new CodeAttributeDeclaration (type, new CodeAttributeArgument (new CodePrimitiveExpression (val))));
372
static void AddWarningDisablePragmas (CodeTypeDeclaration type, CodeDomProvider provider)
374
if (provider is Microsoft.CSharp.CSharpCodeProvider) {
375
type.Members.Add (new CodeSnippetTypeMember ("#pragma warning disable 0169")); // unused member
379
static void GenerateAction (CodeTypeReference actionAtt, CodeTypeDeclaration type, IBAction action,
380
CodeDomProvider provider)
382
var m = CreateEventMethod (actionAtt, action);
383
type.Members.Add (m);
385
if (provider.FileExtension == "pas") {
386
m.UserData ["OxygenePartial"] = "YES";
387
m.UserData ["OxygeneEmpty"] = "YES";
391
static void GenerateReleaseDesignerOutletsMethod (CodeTypeDeclaration type)
393
var thisRef = new CodeThisReferenceExpression ();
394
var nullRef = new CodePrimitiveExpression (null);
396
var meth = new CodeMemberMethod () {
397
Name = "ReleaseDesignerOutlets",
400
foreach (var outlet in type.Members.OfType<CodeMemberProperty> ()) {
401
var propRef = new CodePropertyReferenceExpression (thisRef, outlet.Name);
402
meth.Statements.Add (
403
new CodeConditionStatement (
404
new CodeBinaryOperatorExpression (propRef, CodeBinaryOperatorType.IdentityInequality, nullRef),
405
new CodeExpressionStatement (new CodeMethodInvokeExpression (propRef, "Dispose")),
406
new CodeAssignStatement (propRef, nullRef)
411
type.Members.Add (meth);
414
public static CodeTypeMember CreateEventMethod (CodeTypeReference exportAtt, IBAction action)
416
var meth = new CodeMemberMethod () {
417
Name = action.CliName,
418
ReturnType = new CodeTypeReference (typeof (void)),
420
foreach (var p in action.Parameters) {
421
meth.Parameters.Add (new CodeParameterDeclarationExpression () {
423
Type = new CodeTypeReference (p.ObjCType)
426
AddAttribute (meth.CustomAttributes, exportAtt, action.GetObjcFullName ());
431
public override string ToString ()
433
return string.Format ("[NSObjectTypeInfo: ObjCName={0}, CliName={1}, IsModel={2}, BaseObjCType={3}, BaseCliType={4}, BaseIsModel={5}, IsUserType={6}, IsRegisteredInDesigner={7}, Outlets={8}, Actions={9}, DefinedIn={10}, UserTypeReferences={11}]", ObjCName, CliName, IsModel, BaseObjCType, BaseCliType, BaseIsModel, IsUserType, IsRegisteredInDesigner, Outlets, Actions, DefinedIn, UserTypeReferences);
437
class ObjectiveCGenerationException : Exception
439
NSObjectTypeInfo typeInfo;
441
public ObjectiveCGenerationException (string message, NSObjectTypeInfo typeInfo) : base (message)
443
this.typeInfo = typeInfo;
446
public NSObjectTypeInfo TypeInfo { get { return typeInfo; } }