5
// Mike KrĆ¼ger <mkrueger@xamarin.com>
7
// Copyright (c) 2011 Xamarin <http://xamarin.com>
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;
29
using MonoDevelop.Core;
31
using MonoMac.Foundation;
32
using System.Runtime.InteropServices;
35
using MonoDevelop.Ide;
37
using MonoMac.ObjCRuntime;
39
namespace MonoDevelop.MacDev.PlistEditor
41
public abstract class PObject
43
static readonly IntPtr cls_NSPropertyListSerialization = Class.GetHandle ("NSPropertyListSerialization");
44
static readonly IntPtr sel_dataFromPropertyList_format_options_error = Selector.GetHandle ("dataWithPropertyList:format:options:error:");
46
[DllImport (MonoMac.Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend")]
47
static extern IntPtr IntPtr_objc_msgSend_IntPtr_Int_Int_OutIntPtr (
55
public static PObject Create (string type)
61
return new PDictionary ();
63
return new PBoolean (true);
65
return new PData (new byte[0]);
67
return new PDate (DateTime.Now);
69
return new PNumber (0);
71
return new PString ("");
73
LoggingService.LogError ("Unknown pobject type:" + type);
74
return new PString ("<error>");
78
internal static IEnumerable<KeyValuePair<string, PObject>> ToEnumerable (PObject obj)
80
if (obj is PDictionary) {
81
return (PDictionary) obj;
82
} else if (obj is PArray) {
83
return ((PArray) obj).Select (k => new KeyValuePair<string, PObject> (k is IPValueObject ? ((IPValueObject) k).Value.ToString () : null, k));
85
return Enumerable.Empty <KeyValuePair<string, PObject>> ();
91
public PObject Parent {
96
if (parent != null && value != null)
97
throw new NotSupportedException ("Already parented.");
102
public abstract string TypeString {
106
public void Replace (PObject newObject)
109
if (p is PDictionary) {
110
var dict = (PDictionary)p;
111
var key = dict.GetKey (this);
115
dict[key] = newObject;
116
} else if (p is PArray) {
118
arr.Replace (this, newObject);
124
if (Parent is PDictionary) {
125
var dict = (PDictionary)Parent;
126
if (dict.Any (p => p.Value == this)) {
127
var pair = dict.First (p => p.Value == this);
135
public void Remove ()
137
if (Parent is PDictionary) {
138
var dict = (PDictionary)Parent;
140
} else if (Parent is PArray) {
141
var arr = (PArray)Parent;
145
throw new InvalidOperationException ("Can't remove from null parent");
146
throw new InvalidOperationException ("Can't remove from parent " + Parent);
150
public abstract NSObject Convert ();
152
public abstract void SetValue (string text);
154
public static implicit operator PObject (string value)
156
return new PString (value);
159
public static implicit operator PObject (int value)
161
return new PNumber (value);
164
public static implicit operator PObject (bool value)
166
return new PBoolean (value);
169
public static implicit operator PObject (DateTime value)
171
return new PDate (value);
174
public static implicit operator PObject (byte[] value)
176
return new PData (value);
179
protected virtual void OnChanged (EventArgs e)
181
if (SuppressChangeEvents)
184
EventHandler handler = this.Changed;
189
Parent.OnChanged (e);
192
protected bool SuppressChangeEvents {
196
public event EventHandler Changed;
198
public string ToXml ()
200
using (new NSAutoreleasePool ()) {
201
var errorPtr = IntPtr.Zero;
202
var pobject = Convert ();
204
var nsDataPtr = IntPtr_objc_msgSend_IntPtr_Int_Int_OutIntPtr (
205
cls_NSPropertyListSerialization,
206
sel_dataFromPropertyList_format_options_error,
207
pobject.Handle, (int) NSPropertyListFormat.Xml, 0, out errorPtr);
209
if (errorPtr != IntPtr.Zero) {
210
var error = (NSError) MonoMac.ObjCRuntime.Runtime.GetNSObject (errorPtr);
211
throw new Exception (error.LocalizedDescription);
212
} else if (nsDataPtr == IntPtr.Zero) {
213
throw new Exception ("Could not convert the NSDictionary to xml representation");
215
var nsData = (NSData) MonoMac.ObjCRuntime.Runtime.GetNSObject (nsDataPtr);
216
return System.Text.Encoding.UTF8.GetString (nsData.ToArray ());
222
public abstract class PObjectContainer : PObject
224
public abstract bool Reload (string fileName);
225
public abstract void Save (string fileName);
228
public interface IPValueObject
230
object Value { get; set; }
233
public abstract class PValueObject<T> : PObject, IPValueObject
242
OnChanged (EventArgs.Empty);
246
object IPValueObject.Value {
247
get { return Value; }
248
set { Value = (T) value; }
251
public PValueObject (T value)
256
public PValueObject ()
260
public static implicit operator T (PValueObject<T> pObj)
262
return pObj != null ? pObj.Value : default(T);
266
public class PDictionary : PObjectContainer, IEnumerable<KeyValuePair<string, PObject>>
268
public const string Type = "Dictionary";
269
Dictionary<string, PObject> dict;
272
public override string TypeString {
278
public PObject this[string key] {
284
bool exists = dict.TryGetValue (key, out existing);
291
OnRemoved (new PObjectEventArgs (existing));
292
OnAdded (new PObjectEventArgs (value));
296
public EventHandler<PObjectEventArgs> Added;
298
protected virtual void OnAdded (PObjectEventArgs e)
300
e.PObject.Parent = this;
301
var handler = this.Added;
304
OnChanged (EventArgs.Empty);
307
public void Add (string key, PObject value)
309
dict.Add (key, value);
311
OnAdded (new PObjectEventArgs (value));
314
public void InsertAfter (string keyBefore, string key, PObject value)
316
dict.Add (key, value);
317
order.Insert (order.IndexOf (keyBefore) + 1, key);
318
OnAdded (new PObjectEventArgs (value));
327
#region IEnumerable[KeyValuePair[System.String,PObject]] implementation
328
public IEnumerator<KeyValuePair<string, PObject>> GetEnumerator ()
330
foreach (var key in order)
331
yield return new KeyValuePair<string, PObject> (key, dict[key]);
335
#region IEnumerable implementation
336
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
338
return GetEnumerator ();
342
public PDictionary ()
344
dict = new Dictionary<string, PObject> ();
345
order = new List<string> ();
348
public bool ContainsKey (string name)
350
return dict.ContainsKey (name);
353
public EventHandler<PObjectEventArgs> Removed;
355
protected virtual void OnRemoved (PObjectEventArgs e)
357
e.PObject.Parent = null;
358
var handler = this.Removed;
361
OnChanged (EventArgs.Empty);
364
public bool Remove (string key)
367
if (dict.TryGetValue (key, out obj)) {
370
OnRemoved (new PObjectEventArgs (obj));
376
public bool ChangeKey (PObject obj, string newKey)
378
return ChangeKey (obj, newKey, null);
381
public bool ChangeKey (PObject obj, string newKey, PObject newValue)
383
var oldkey = GetKey (obj);
384
if (oldkey == null || dict.ContainsKey (newKey))
387
dict.Remove (oldkey);
388
dict.Add (newKey, newValue ?? obj);
389
order[order.IndexOf (oldkey)] = newKey;
390
if (newValue != null) {
391
OnRemoved (new PObjectEventArgs (obj));
392
OnAdded (new PObjectEventArgs (newValue));
394
OnChanged (EventArgs.Empty);
399
public string GetKey (PObject obj)
401
foreach (var pair in dict) {
402
if (pair.Value == obj)
408
public T Get<T> (string key) where T : PObject
411
if (!dict.TryGetValue (key, out obj))
417
public bool TryGetValue<T> (string key, out T value) where T : PObject
420
if (!dict.TryGetValue (key, out obj)) {
434
public override void SetValue (string text)
436
throw new NotSupportedException ();
439
public override NSObject Convert ()
441
List<NSObject> objs = new List<NSObject> ();
442
List<NSObject> keys = new List<NSObject> ();
444
foreach (var key in order) {
445
var val = dict[key].Convert ();
447
keys.Add (new NSString (key));
449
return NSDictionary.FromObjectsAndKeys (objs.ToArray (), keys.ToArray ());
452
public override void Save (string fileName)
454
using (new NSAutoreleasePool ()) {
455
var dict = (NSDictionary)Convert ();
456
dict.WriteToFile (fileName, false);
460
public static PDictionary Load (string fileName)
462
using (new NSAutoreleasePool ()) {
463
var dict = NSDictionary.FromFile (fileName);
464
return (PDictionary)Conv (dict);
468
public override bool Reload (string fileName)
470
if (string.IsNullOrEmpty (fileName))
471
throw new ArgumentNullException ("fileName");
472
var pool = new NSAutoreleasePool ();
473
SuppressChangeEvents = true;
477
var nsd = NSDictionary.FromFile (fileName);
479
foreach (var pair in nsd) {
480
string k = pair.Key.ToString ();
481
this [k] = Conv (pair.Value);
487
SuppressChangeEvents = false;
490
OnChanged (EventArgs.Empty);
494
static IntPtr selObjCType = MonoMac.ObjCRuntime.Selector.GetHandle ("objCType");
495
internal static PObject Conv (NSObject val)
499
if (val is NSDictionary) {
500
var result = new PDictionary ();
501
foreach (var pair in (NSDictionary)val) {
502
string k = pair.Key.ToString ();
503
result[k] = Conv (pair.Value);
508
if (val is NSArray) {
509
var result = new PArray ();
510
var arr = NSArray.ArrayFromHandle<NSObject> (((NSArray)val).Handle);
513
foreach (var f in arr) {
515
result.Add (Conv (f));
521
return ((NSString)val).ToString ();
522
if (val is NSNumber) {
523
var nr = (NSNumber)val;
524
var str = Marshal.PtrToStringAnsi (MonoMac.ObjCRuntime.Messaging.IntPtr_objc_msgSend (val.Handle, selObjCType));
525
if (str == "c" || str == "C" || str == "B")
527
return nr.Int32Value;
530
return PDate.referenceDate + TimeSpan.FromSeconds (((NSDate)val).SecondsSinceReferenceDate);
533
var data = (NSData)val;
534
var bytes = new byte[data.Length];
535
Marshal.Copy (data.Bytes, bytes, 0, (int)data.Length);
539
throw new NotSupportedException (val.ToString ());
542
public override string ToString ()
544
return string.Format ("[PDictionary: Items={0}]", dict.Count);
547
public void SetString (string key, string value)
549
var result = Get<PString> (key);
550
if (result == null) {
551
this[key] = result = new PString (value);
554
result.Value = value;
555
OnChanged (EventArgs.Empty);
558
public PString GetString (string key)
560
var result = Get<PString> (key);
561
if (result == null) {
562
this[key] = result = new PString ("");
567
public PArray GetArray (string key)
569
var result = Get<PArray> (key);
570
if (result == null) {
571
this[key] = result = new PArray ();
577
public class PArray : PObjectContainer, IEnumerable<PObject>
579
public const string Type = "Array";
582
public override string TypeString {
594
public PObject this[int i] {
602
list = new List<PObject> ();
605
public EventHandler<PObjectEventArgs> Added;
607
protected virtual void OnAdded (PObjectEventArgs e)
609
e.PObject.Parent = this;
610
if (SuppressChangeEvents)
613
var handler = this.Added;
616
OnChanged (EventArgs.Empty);
619
public override bool Reload (string fileName)
621
if (string.IsNullOrEmpty (fileName))
622
throw new ArgumentNullException ("fileName");
623
var pool = new NSAutoreleasePool ();
624
SuppressChangeEvents = true;
627
var nsa = NSArray.FromFile (fileName);
629
var arr = NSArray.ArrayFromHandle<NSObject> (nsa.Handle);
630
foreach (var f in arr) {
631
Add (PDictionary.Conv (f));
637
SuppressChangeEvents = false;
640
OnChanged (EventArgs.Empty);
644
public override void Save (string fileName)
646
using (new NSAutoreleasePool ()) {
647
var arr = (NSArray)Convert ();
648
arr.WriteToFile (fileName, false);
652
public void Add (PObject obj)
655
OnAdded (new PObjectEventArgs (obj));
658
public void Replace (PObject oldObj, PObject newObject)
660
for (int i = 0; i < Count; i++) {
661
if (list[i] == oldObj) {
663
OnRemoved (new PObjectEventArgs (oldObj));
664
OnAdded (new PObjectEventArgs (newObject));
670
public EventHandler<PObjectEventArgs> Removed;
672
protected virtual void OnRemoved (PObjectEventArgs e)
674
e.PObject.Parent = null;
675
if (SuppressChangeEvents)
678
var handler = this.Removed;
681
OnChanged (EventArgs.Empty);
684
public void Remove (PObject obj)
686
if (list.Remove (obj))
687
OnRemoved (new PObjectEventArgs (obj));
693
OnChanged (EventArgs.Empty);
696
public override void SetValue (string text)
698
throw new NotSupportedException ();
701
public override NSObject Convert ()
703
return NSArray.FromNSObjects (list.Select (x => x.Convert ()).ToArray ());
706
public override string ToString ()
708
return string.Format ("[PArray: Items={0}]", Count);
711
public void AssignStringList (string strList)
713
SuppressChangeEvents = true;
716
foreach (var item in strList.Split (',', ' ')) {
717
if (string.IsNullOrEmpty (item))
719
Add (new PString (item));
722
SuppressChangeEvents = false;
723
OnChanged (EventArgs.Empty);
727
public string ToStringList ()
729
var sb = new StringBuilder ();
730
foreach (PString str in list.Where (o => o is PString)) {
735
return sb.ToString ();
738
#region IEnumerable[PObject] implementation
739
public IEnumerator<PObject> GetEnumerator ()
741
return list.GetEnumerator ();
745
#region IEnumerable implementation
746
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
748
return ((System.Collections.IEnumerable)list).GetEnumerator ();
753
public class PBoolean : PValueObject<bool>
755
public const string Yes = "Yes";
756
public const string No = "No";
758
public const string Type = "Boolean";
760
public override string TypeString {
766
public PBoolean (bool value) : base(value)
770
public override void SetValue (string text)
772
if (text == Yes || text == GettextCatalog.GetString ("Yes"))
774
else if (text == No || text == GettextCatalog.GetString ("No"))
778
public override NSObject Convert ()
780
return NSNumber.FromBoolean (Value);
784
public class PData : PValueObject<byte[]>
786
public const string Type = "Data";
787
static readonly byte[] Empty = new byte [0];
789
public override string TypeString {
795
public override NSObject Convert ()
797
// Work around a bug in NSData.FromArray as it cannot (currently) handle
798
// zero length arrays
799
if (Value.Length == 0)
800
return new NSData ();
802
return NSData.FromArray (Value);
805
public PData (byte[] value) : base(value ?? Empty)
809
public override void SetValue (string text)
811
throw new NotSupportedException ();
815
public class PDate : PValueObject<DateTime>
817
public const string Type = "Date";
818
internal static DateTime referenceDate = new DateTime (2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
820
public override string TypeString {
826
public PDate (DateTime value) : base(value)
830
public override void SetValue (string text)
832
throw new NotImplementedException ();
835
public override NSObject Convert ()
837
var secs = (Value - referenceDate).TotalSeconds;
838
return NSDate.FromTimeIntervalSinceReferenceDate (secs);
842
public class PNumber : PValueObject<int>
844
public const string Type = "Number";
846
public override string TypeString {
852
public PNumber (int value) : base(value)
856
public override void SetValue (string text)
859
if (int.TryParse (text, out newValue))
863
public override NSObject Convert ()
865
return NSNumber.FromInt32 (Value);
869
public class PString : PValueObject<string>
871
public const string Type = "String";
873
public override string TypeString {
879
public PString (string value) : base(value)
882
throw new ArgumentNullException ("value");
885
public override void SetValue (string text)
888
throw new ArgumentNullException ("text");
892
public override NSObject Convert ()
894
return new NSString (Value);
899
public sealed class PObjectEventArgs : EventArgs
901
public PObject PObject {
906
public PObjectEventArgs (PObject pObject)
908
this.PObject = pObject;