7
// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
using System.Collections;
31
using System.Collections.Generic;
32
using System.Reflection;
34
namespace MonoDevelop.Core.Serialization
36
public class ClassDataType: DataType
38
Hashtable properties = new Hashtable ();
39
List<ItemProperty> sortedPoperties = new List<ItemProperty> ();
43
public ClassDataType (Type propType): base (propType)
47
public override bool IsSimpleType { get { return false; } }
48
public override bool CanCreateInstance { get { return true; } }
49
public override bool CanReuseInstance { get { return true; } }
51
protected override void Initialize ()
53
DataItemAttribute atd = (DataItemAttribute) Context.AttributeProvider.GetCustomAttribute (ValueType, typeof(DataItemAttribute), false);
55
if (!string.IsNullOrEmpty (atd.Name)) {
58
if (atd.FallbackType != null) {
59
fallbackType = atd.FallbackType;
60
if (!typeof(IExtendedDataItem).IsAssignableFrom (fallbackType))
61
throw new InvalidOperationException ("Fallback type '" + fallbackType + "' must implement IExtendedDataItem");
62
if (!ValueType.IsAssignableFrom (fallbackType))
63
throw new InvalidOperationException ("Fallback type '" + fallbackType + "' must be a subclass of '" + ValueType + "'");
67
object[] incs = Attribute.GetCustomAttributes (ValueType, typeof (DataIncludeAttribute), true);
68
foreach (DataIncludeAttribute incat in incs) {
69
Context.IncludeType (incat.Type);
72
if (ValueType.BaseType != null) {
73
ClassDataType baseType = (ClassDataType) Context.GetConfigurationDataType (ValueType.BaseType);
74
baseType.AddSubtype (this);
76
foreach (ItemProperty prop in baseType.Properties) {
77
properties.Add (prop.Name, prop);
78
sortedPoperties.Insert (n++, prop);
81
// Inherit the fallback type
82
if (fallbackType == null && baseType.fallbackType != null)
83
fallbackType = baseType.fallbackType;
86
foreach (Type interf in ValueType.GetInterfaces ()) {
87
ClassDataType baseType = (ClassDataType) Context.GetConfigurationDataType (interf);
88
baseType.AddSubtype (this);
91
MemberInfo[] members = ValueType.GetMembers (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
92
foreach (MemberInfo member in members) {
93
if ((member is FieldInfo || member is PropertyInfo) && member.DeclaringType == ValueType) {
94
Type memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;
95
AddProperty (member, member.Name, memberType);
99
foreach (ItemMember member in Context.AttributeProvider.GetItemMembers (ValueType)) {
100
AddProperty (member, member.Name, member.Type);
103
if (fallbackType != null)
104
Context.IncludeType (fallbackType);
107
void AddProperty (object member, string name, Type memberType)
109
object[] ats = Context.AttributeProvider.GetCustomAttributes (member, typeof(Attribute), true);
111
ItemPropertyAttribute at = FindPropertyAttribute (ats, "");
115
ItemProperty prop = new ItemProperty ();
116
prop.Name = (at.Name != null) ? at.Name : name;
117
prop.ExpandedCollection = Context.AttributeProvider.IsDefined (member, typeof(ExpandedCollectionAttribute), true);
118
prop.DefaultValue = at.DefaultValue;
119
prop.IsExternal = at.IsExternal;
120
prop.SkipEmpty = at.SkipEmpty;
122
if (prop.ExpandedCollection) {
123
ICollectionHandler handler = Context.GetCollectionHandler (memberType);
125
throw new InvalidOperationException ("ExpandedCollectionAttribute can't be applied to property '" + prop.Name + "' in type '" + ValueType + "' becuase it is not a valid collection.");
127
memberType = handler.GetItemType ();
128
prop.ExpandedCollectionHandler = handler;
131
if (at.ValueType != null)
132
prop.PropertyType = at.ValueType;
134
prop.PropertyType = memberType;
136
if (at.SerializationDataType != null) {
138
prop.DataType = (DataType) Activator.CreateInstance (at.SerializationDataType, new object[] { prop.PropertyType } );
139
} catch (MissingMethodException ex) {
140
throw new InvalidOperationException ("Constructor not found for custom data type: " + at.SerializationDataType.Name + " (Type propertyType);", ex);
144
if (member is MemberInfo) {
145
prop.Member = (MemberInfo) member;
148
prop.InitValue = ((ItemMember)member).InitValue;
149
AddProperty (prop, ((ItemMember)member).InsertBefore);
152
prop.Initialize (ats, "");
154
if (prop.ExpandedCollection && prop.DataType.IsSimpleType)
155
throw new InvalidOperationException ("ExpandedCollectionAttribute is not allowed in collections of simple types");
158
internal protected override object GetMapData (object[] attributes, string scope)
160
// We only need the fallback type for now
162
ItemPropertyAttribute at = FindPropertyAttribute (attributes, scope);
164
return at.FallbackType;
169
private void AddSubtype (ClassDataType subtype)
171
if (subtypes == null)
172
subtypes = new ArrayList ();
173
subtypes.Add (subtype);
176
ICollection Properties {
177
get { return sortedPoperties; }
180
public void AddProperty (ItemProperty prop)
182
AddProperty (prop, null);
185
void AddProperty (ItemProperty prop, string insertBefore)
187
if (!prop.IsNested) {
188
foreach (ItemProperty p in sortedPoperties) {
189
if (p.IsNested && p.NameList[0] == prop.Name)
190
throw CreateNestedConflictException (prop, p);
193
ItemProperty p = properties [prop.NameList[0]] as ItemProperty;
195
throw CreateNestedConflictException (prop, p);
198
prop.SetContext (Context);
199
if (properties.ContainsKey (prop.Name))
200
throw new InvalidOperationException ("Duplicate property '" + prop.Name + "' in class '" + ValueType);
202
properties.Add (prop.Name, prop);
204
if (insertBefore != null) {
206
for (int n=0; n<sortedPoperties.Count; n++) {
207
ItemProperty p = sortedPoperties [n];
208
if (p.MemberName == insertBefore) {
209
sortedPoperties.Insert (n, prop);
215
sortedPoperties.Add (prop);
217
else if (prop.Unsorted) {
218
int foundPos = sortedPoperties.Count;
219
for (int n=0; n < sortedPoperties.Count; n++) {
220
ItemProperty cp = (ItemProperty) sortedPoperties [n];
221
if (cp.Unsorted && prop.Name.CompareTo (cp.Name) < 0) {
226
sortedPoperties.Insert (foundPos, prop);
228
sortedPoperties.Add (prop);
230
if (subtypes != null && subtypes.Count > 0) {
231
foreach (ClassDataType subtype in subtypes)
232
subtype.AddProperty (prop);
236
public void RemoveProperty (string name)
238
ItemProperty prop = (ItemProperty) properties [name];
241
properties.Remove (name);
242
sortedPoperties.Remove (prop);
244
if (subtypes != null && subtypes.Count > 0) {
245
foreach (ClassDataType subtype in subtypes)
246
subtype.RemoveProperty (name);
250
public IEnumerable<ItemProperty> GetProperties (SerializationContext serCtx, object instance)
252
foreach (ItemProperty prop in sortedPoperties) {
253
if (serCtx.Serializer.CanHandleProperty (prop, serCtx, instance))
258
Exception CreateNestedConflictException (ItemProperty p1, ItemProperty p2)
260
return new InvalidOperationException ("There is a conflict between the properties '" + p1.Name + "' and '" + p2.Name + "'. Nested element properties can't be mixed with normal element properties.");
263
internal protected override DataNode OnSerialize (SerializationContext serCtx, object mapData, object obj)
267
if (obj.GetType () != ValueType) {
268
if (obj is IExtendedDataItem) {
269
// This is set by fallback types, to make sure the original type name is serialized back
270
ctype = (string) ((IExtendedDataItem)obj).ExtendedProperties ["__raw_ctype"];
273
DataType subtype = Context.GetConfigurationDataType (obj.GetType ());
274
DataItem it = (DataItem) subtype.Serialize (serCtx, mapData, obj);
275
it.ItemData.Add (new DataValue ("ctype", subtype.Name));
281
DataItem item = new DataItem ();
284
ICustomDataItem citem = Context.AttributeProvider.GetCustomDataItem (obj);
286
ClassTypeHandler handler = new ClassTypeHandler (serCtx, this);
287
item.ItemData = citem.Serialize (handler);
290
item.ItemData = Serialize (serCtx, obj);
293
item.ItemData.Add (new DataValue ("ctype", ctype));
298
internal DataCollection Serialize (SerializationContext serCtx, object obj)
300
DataCollection itemCol = new DataCollection ();
302
foreach (ItemProperty prop in Properties) {
303
if (prop.ReadOnly || !prop.CanSerialize (serCtx, obj))
305
object val = prop.GetValue (obj);
308
if (val.Equals (prop.DefaultValue))
311
DataCollection col = itemCol;
313
col = GetNestedCollection (col, prop.NameList, 0);
315
if (prop.ExpandedCollection) {
316
ICollectionHandler handler = prop.ExpandedCollectionHandler;
317
object pos = handler.GetInitialPosition (val);
318
while (handler.MoveNextItem (val, ref pos)) {
319
object item = handler.GetCurrentItem (val, pos);
320
if (item == null) continue;
321
DataNode data = prop.Serialize (serCtx, obj, item);
322
data.Name = prop.SingleName;
327
DataNode data = prop.Serialize (serCtx, obj, val);
334
if (obj is IExtendedDataItem) {
335
// Serialize raw data which could not be deserialized
336
DataItem uknData = (DataItem) ((IExtendedDataItem)obj).ExtendedProperties ["__raw_data"];
338
itemCol.Merge (uknData.ItemData);
344
DataCollection GetNestedCollection (DataCollection col, string[] nameList, int pos)
346
if (pos == nameList.Length - 1) return col;
348
DataItem item = col [nameList[pos]] as DataItem;
350
item = new DataItem ();
351
item.Name = nameList[pos];
354
return GetNestedCollection (item.ItemData, nameList, pos + 1);
357
internal protected override object OnDeserialize (SerializationContext serCtx, object mapData, DataNode data)
359
DataItem item = data as DataItem;
361
throw new InvalidOperationException ("Invalid value found for type '" + Name + "' " + data);
363
DataValue ctype = item ["ctype"] as DataValue;
364
if (ctype != null && ctype.Value != Name) {
366
DataType stype = FindDerivedType (ctype.Value, mapData, out isFallbackType);
368
if (isFallbackType) {
369
// Remove the ctype attribute, to make sure it is not checked again
370
// by the fallback type
371
item.ItemData.Remove (ctype);
375
object sobj = stype.Deserialize (serCtx, mapData, data);
377
// Store the original data type, so it can be serialized back
378
if (isFallbackType && sobj is IExtendedDataItem) {
379
((IExtendedDataItem)sobj).ExtendedProperties ["__raw_ctype"] = ctype.Value;
385
throw new InvalidOperationException ("Type not found: " + ctype.Value);
388
ConstructorInfo ctor = ValueType.GetConstructor (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
389
if (ctor == null) throw new InvalidOperationException ("Default constructor not found for type '" + ValueType + "'");
391
object obj = ctor.Invoke (null);
392
Deserialize (serCtx, null, item, obj);
396
internal protected override object OnCreateInstance (SerializationContext serCtx, DataNode data)
398
DataItem item = data as DataItem;
400
throw new InvalidOperationException ("Invalid value found for type '" + Name + "'");
402
DataValue ctype = item ["ctype"] as DataValue;
403
if (ctype != null && ctype.Value != Name) {
405
DataType stype = FindDerivedType (ctype.Value, null, out isFallbackType);
406
if (isFallbackType) {
407
// Remove the ctype attribute, to make sure it is not checked again
408
// by the fallback type
409
item.ItemData.Remove (ctype);
412
object sobj = stype.CreateInstance (serCtx, data);
413
// Store the original data type, so it can be serialized back
414
if (isFallbackType && sobj is IExtendedDataItem)
415
((IExtendedDataItem)sobj).ExtendedProperties ["__raw_ctype"] = ctype;
418
else throw new InvalidOperationException ("Type not found: " + ctype.Value);
421
ConstructorInfo ctor = ValueType.GetConstructor (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
422
if (ctor == null) throw new InvalidOperationException ("Default constructor not found for type '" + ValueType + "'");
424
return ctor.Invoke (null);
427
protected internal override void OnDeserialize (SerializationContext serCtx, object mapData, DataNode data, object obj)
429
DataItem item = (DataItem) data;
430
ICustomDataItem citem = Context.AttributeProvider.GetCustomDataItem (obj);
432
ClassTypeHandler handler = new ClassTypeHandler (serCtx, this);
433
citem.Deserialize (handler, item.ItemData);
436
DeserializeNoCustom (serCtx, obj, item.ItemData);
439
internal void DeserializeNoCustom (SerializationContext serCtx, object obj, DataCollection itemData)
441
foreach (ItemProperty prop in Properties)
442
if (!prop.CanDeserialize (serCtx, obj) && prop.DefaultValue != null)
443
prop.SetValue (obj, prop.DefaultValue);
445
// ukwnDataRoot is where to store values for which a property cannot be found.
446
DataItem ukwnDataRoot = (obj is IExtendedDataItem) ? new DataItem () : null;
448
Deserialize (serCtx, obj, itemData, ukwnDataRoot, "");
450
// store unreadable raw data to a special property so it can be
451
// serialized back an the original format is kept
452
if (ukwnDataRoot != null && ukwnDataRoot.HasItemData)
453
((IExtendedDataItem)obj).ExtendedProperties ["__raw_data"] = ukwnDataRoot;
456
void Deserialize (SerializationContext serCtx, object obj, DataCollection itemData, DataItem ukwnDataRoot, string baseName)
458
Hashtable expandedCollections = null;
460
foreach (DataNode value in itemData) {
461
ItemProperty prop = (ItemProperty) properties [baseName + value.Name];
463
if (value is DataItem) {
464
DataItem root = new DataItem ();
465
root.Name = value.Name;
466
root.UniqueNames = ((DataItem)value).UniqueNames;
467
if (ukwnDataRoot != null)
468
ukwnDataRoot.ItemData.Add (root);
469
Deserialize (serCtx, obj, ((DataItem)value).ItemData, root, baseName + value.Name + "/");
471
// If no unknown data has been added, there is no need to keep this
472
// in the unknown items list.
473
if (ukwnDataRoot != null && !root.HasItemData)
474
ukwnDataRoot.ItemData.Remove (root);
476
else if (obj is IExtendedDataItem && (value.Name != "ctype" || baseName.Length > 0)) {
477
// store unreadable raw data to a special property so it can be
478
// serialized back an the original format is kept
479
// The ctype attribute don't need to be stored for the root object, since
480
// it is generated by the serializer
481
ukwnDataRoot.ItemData.Add (value);
485
if (prop.WriteOnly || !prop.CanDeserialize (serCtx, obj))
489
if (prop.ExpandedCollection) {
490
ICollectionHandler handler = prop.ExpandedCollectionHandler;
491
if (expandedCollections == null) expandedCollections = new Hashtable ();
494
if (!expandedCollections.ContainsKey (prop)) {
495
col = handler.CreateCollection (out pos, -1);
497
pos = expandedCollections [prop];
498
col = prop.GetValue (obj);
500
handler.AddItem (ref col, ref pos, prop.Deserialize (serCtx, obj, value));
501
expandedCollections [prop] = pos;
502
prop.SetValue (obj, col);
505
if (prop.HasSetter && prop.DataType.CanCreateInstance)
506
prop.SetValue (obj, prop.Deserialize (serCtx, obj, value));
507
else if (prop.DataType.CanReuseInstance) {
508
object pval = prop.GetValue (obj);
511
throw new InvalidOperationException ("The property '" + prop.Name + "' is null and a new instance of '" + prop.PropertyType + "' can't be created.");
513
throw new InvalidOperationException ("The property '" + prop.Name + "' is null and it does not have a setter.");
515
prop.Deserialize (serCtx, obj, value, pval);
517
throw new InvalidOperationException ("The property does not have a setter.");
521
catch (Exception ex) {
522
throw new InvalidOperationException ("Could not set property '" + prop.Name + "' in type '" + Name + "'", ex);
527
DataType FindDerivedType (string name, object mapData, out bool isFallbackType)
529
isFallbackType = false;
530
if (subtypes != null) {
531
foreach (ClassDataType stype in subtypes) {
532
if (stype.Name == name)
536
DataType cst = stype.FindDerivedType (name, null, out fb);
537
if (cst != null && !fb) {
538
isFallbackType = false;
543
if (mapData != null) {
544
isFallbackType = true;
545
return Context.GetConfigurationDataType ((Type)mapData);
548
if (fallbackType != null) {
549
isFallbackType = true;
550
return Context.GetConfigurationDataType (fallbackType);
557
internal class ClassTypeHandler: ITypeSerializer
559
SerializationContext ctx;
562
internal ClassTypeHandler (SerializationContext ctx, ClassDataType cdt)
568
public DataCollection Serialize (object instance)
570
return cdt.Serialize (ctx, instance);
573
public void Deserialize (object instance, DataCollection data)
575
cdt.DeserializeNoCustom (ctx, instance, data);
578
public SerializationContext SerializationContext {