2 // System.Runtime.Serialization.ObjectManager.cs
4 // Author: Lluis Sanchez Gual (lluis@ideary.com)
6 // (C) 2003 Lluis Sanchez Gual
10 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Reflection;
36 namespace System.Runtime.Serialization
38 [System.Runtime.InteropServices.ComVisibleAttribute (true)]
39 public class ObjectManager
41 // All objects are chained in the same order as they have been registered
42 ObjectRecord _objectRecordChain = null;
43 ObjectRecord _lastObjectRecord = null;
45 ArrayList _deserializedRecords = new ArrayList();
46 ArrayList _onDeserializedCallbackRecords = new ArrayList();
47 Hashtable _objectRecords = new Hashtable();
48 bool _finalFixup = false;
50 ISurrogateSelector _selector;
51 StreamingContext _context;
52 int _registeredObjectsCount = 0;
54 public ObjectManager(ISurrogateSelector selector, StreamingContext context)
60 public virtual void DoFixups()
66 if (_registeredObjectsCount < _objectRecords.Count)
67 throw new SerializationException ("There are some fixups that refer to objects that have not been registered");
70 ObjectRecord last = _lastObjectRecord;
72 bool firstCycle = true;
73 bool lastCycle = false;
74 int unresolvedCount = 0; // Unresolved objects found in the current cycle
75 int lastUnresolvedCount = 0; // Unresolved objects before the current cycle
77 // Solve al pending fixups of all objects
79 ObjectRecord record = _objectRecordChain;
80 while (record != null)
82 // We ignore object references in the first cycle
83 bool ready = !(record.IsUnsolvedObjectReference && firstCycle);
84 if (ready) ready = record.DoFixups (true, this, true);
85 if (ready) ready = record.LoadData(this, _selector, _context);
91 if (record.OriginalObject is IDeserializationCallback)
92 _deserializedRecords.Add (record);
94 SerializationCallbacks sc = SerializationCallbacks
95 .GetSerializationCallbacks (record.OriginalObject.GetType ());
96 if (sc.HasDeserializedCallbacks)
97 _onDeserializedCallbackRecords.Add (record);
102 // There must be an unresolved IObjectReference instance.
103 // Chain the record at the end so it is solved later
105 if ((record.ObjectInstance is IObjectReference) && !firstCycle)
107 if (record.IsUnsolvedObjectReference && lastCycle)
108 // No more chances to resolve
109 throw new SerializationException ("The object with ID " + record.ObjectID + " could not be resolved");
115 if (record != _lastObjectRecord) {
118 _lastObjectRecord.Next = record;
119 _lastObjectRecord = record;
125 if (record == last) {
126 last = _lastObjectRecord;
130 if (lastUnresolvedCount == unresolvedCount)
133 lastUnresolvedCount = unresolvedCount;
145 internal ObjectRecord GetObjectRecord (long objectID)
147 ObjectRecord rec = (ObjectRecord)_objectRecords[objectID];
150 if (_finalFixup) throw new SerializationException ("The object with Id " + objectID + " has not been registered");
151 rec = new ObjectRecord();
152 rec.ObjectID = objectID;
153 _objectRecords[objectID] = rec;
155 if (!rec.IsRegistered && _finalFixup) throw new SerializationException ("The object with Id " + objectID + " has not been registered");
159 public virtual object GetObject (long objectID)
161 if (objectID <= 0) throw new ArgumentOutOfRangeException("objectID","The objectID parameter is less than or equal to zero");
162 ObjectRecord rec = (ObjectRecord)_objectRecords[objectID];
163 if (rec == null || !rec.IsRegistered) return null;
164 else return rec.ObjectInstance;
167 public virtual void RaiseDeserializationEvent ()
169 for (int i = _onDeserializedCallbackRecords.Count-1; i >= 0; i--)
171 ObjectRecord record = (ObjectRecord) _onDeserializedCallbackRecords [i];
172 RaiseOnDeserializedEvent (record.OriginalObject);
174 for (int i = _deserializedRecords.Count-1; i >= 0; i--)
176 ObjectRecord record = (ObjectRecord) _deserializedRecords [i];
177 IDeserializationCallback obj = record.OriginalObject as IDeserializationCallback;
178 if (obj != null) obj.OnDeserialization (this);
183 public void RaiseOnDeserializingEvent (object obj)
185 SerializationCallbacks sc = SerializationCallbacks
186 .GetSerializationCallbacks (obj.GetType ());
187 sc.RaiseOnDeserializing (obj, _context);
190 void RaiseOnDeserializedEvent (object obj)
192 SerializationCallbacks sc = SerializationCallbacks
193 .GetSerializationCallbacks (obj.GetType ());
194 sc.RaiseOnDeserialized (obj, _context);
197 private void AddFixup (BaseFixupRecord record)
199 record.ObjectToBeFixed.ChainFixup (record, true);
200 record.ObjectRequired.ChainFixup (record, false);
203 public virtual void RecordArrayElementFixup (long arrayToBeFixed, int index, long objectRequired)
205 if (arrayToBeFixed <= 0) throw new ArgumentOutOfRangeException("arrayToBeFixed","The arrayToBeFixed parameter is less than or equal to zero");
206 if (objectRequired <= 0) throw new ArgumentOutOfRangeException("objectRequired","The objectRequired parameter is less than or equal to zero");
207 ArrayFixupRecord record = new ArrayFixupRecord(GetObjectRecord(arrayToBeFixed), index, GetObjectRecord(objectRequired));
211 public virtual void RecordArrayElementFixup (long arrayToBeFixed, int[] indices, long objectRequired)
213 if (arrayToBeFixed <= 0) throw new ArgumentOutOfRangeException("arrayToBeFixed","The arrayToBeFixed parameter is less than or equal to zero");
214 if (objectRequired <= 0) throw new ArgumentOutOfRangeException("objectRequired","The objectRequired parameter is less than or equal to zero");
215 if (indices == null) throw new ArgumentNullException("indices");
216 MultiArrayFixupRecord record = new MultiArrayFixupRecord (GetObjectRecord(arrayToBeFixed), indices, GetObjectRecord(objectRequired));
220 public virtual void RecordDelayedFixup (long objectToBeFixed, string memberName, long objectRequired)
222 if (objectToBeFixed <= 0) throw new ArgumentOutOfRangeException("objectToBeFixed","The objectToBeFixed parameter is less than or equal to zero");
223 if (objectRequired <= 0) throw new ArgumentOutOfRangeException("objectRequired","The objectRequired parameter is less than or equal to zero");
224 if (memberName == null) throw new ArgumentNullException("memberName");
225 DelayedFixupRecord record = new DelayedFixupRecord (GetObjectRecord(objectToBeFixed), memberName, GetObjectRecord(objectRequired));
229 public virtual void RecordFixup (long objectToBeFixed, MemberInfo member, long objectRequired)
231 if (objectToBeFixed <= 0) throw new ArgumentOutOfRangeException("objectToBeFixed","The objectToBeFixed parameter is less than or equal to zero");
232 if (objectRequired <= 0) throw new ArgumentOutOfRangeException("objectRequired","The objectRequired parameter is less than or equal to zero");
233 if (member == null) throw new ArgumentNullException("member");
234 FixupRecord record = new FixupRecord (GetObjectRecord(objectToBeFixed), member, GetObjectRecord(objectRequired));
238 private void RegisterObjectInternal (object obj, ObjectRecord record)
240 if (obj == null) throw new ArgumentNullException("obj");
242 if (record.IsRegistered)
244 if (record.OriginalObject != obj) throw new SerializationException ("An object with Id " + record.ObjectID + " has already been registered");
248 record.ObjectInstance = obj;
249 record.OriginalObject = obj;
251 if (obj is IObjectReference) record.Status = ObjectRecordStatus.ReferenceUnsolved;
252 else record.Status = ObjectRecordStatus.ReferenceSolved;
254 if (_selector != null) {
255 record.Surrogate = _selector.GetSurrogate (
256 obj.GetType(), _context, out record.SurrogateSelector);
257 if (record.Surrogate != null)
258 record.Status = ObjectRecordStatus.ReferenceUnsolved;
261 record.DoFixups (true, this, false);
262 record.DoFixups (false, this, false);
263 _registeredObjectsCount++;
265 // Adds the object to the chain of registered objects. This chain
266 // is needed to be able to to perform the final fixups in the right order
268 if (_objectRecordChain == null)
270 _objectRecordChain = record;
271 _lastObjectRecord = record;
275 _lastObjectRecord.Next = record;
276 _lastObjectRecord = record;
281 public virtual void RegisterObject (object obj, long objectID)
283 if (obj == null) throw new ArgumentNullException("obj", "The obj parameter is null.");
284 if (objectID <= 0) throw new ArgumentOutOfRangeException("objectID","The objectID parameter is less than or equal to zero");
285 RegisterObjectInternal (obj, GetObjectRecord (objectID));
288 public void RegisterObject (object obj, long objectID, SerializationInfo info)
290 if (obj == null) throw new ArgumentNullException("obj", "The obj parameter is null.");
291 if (objectID <= 0) throw new ArgumentOutOfRangeException("objectID","The objectID parameter is less than or equal to zero");
293 ObjectRecord record = GetObjectRecord (objectID);
295 RegisterObjectInternal (obj, record);
298 public void RegisterObject (object obj, long objectID, SerializationInfo info, long idOfContainingObj, MemberInfo member)
300 RegisterObject (obj, objectID, info, idOfContainingObj, member, null);
303 public void RegisterObject( object obj, long objectID, SerializationInfo info, long idOfContainingObj, MemberInfo member, int[] arrayIndex)
305 if (obj == null) throw new ArgumentNullException("obj", "The obj parameter is null.");
306 if (objectID <= 0) throw new ArgumentOutOfRangeException("objectID","The objectID parameter is less than or equal to zero");
308 ObjectRecord record = GetObjectRecord (objectID);
310 record.IdOfContainingObj = idOfContainingObj;
311 record.Member = member;
312 record.ArrayIndex = arrayIndex;
313 RegisterObjectInternal (obj, record);
319 // Fixup types. There is a fixup class for each fixup type.
322 // Base class for all fixups
324 internal abstract class BaseFixupRecord
326 public BaseFixupRecord(ObjectRecord objectToBeFixed, ObjectRecord objectRequired)
328 ObjectToBeFixed = objectToBeFixed;
329 ObjectRequired = objectRequired;
332 public bool DoFixup (ObjectManager manager, bool strict)
334 if (ObjectToBeFixed.IsRegistered && ObjectRequired.IsInstanceReady)
341 if (!ObjectToBeFixed.IsRegistered) throw new SerializationException ("An object with ID " + ObjectToBeFixed.ObjectID + " was included in a fixup, but it has not been registered");
342 else if (!ObjectRequired.IsRegistered) throw new SerializationException ("An object with ID " + ObjectRequired.ObjectID + " was included in a fixup, but it has not been registered");
349 protected abstract void FixupImpl (ObjectManager manager);
351 internal protected ObjectRecord ObjectToBeFixed;
352 internal protected ObjectRecord ObjectRequired;
354 public BaseFixupRecord NextSameContainer;
355 public BaseFixupRecord NextSameRequired;
359 // Fixup for assigning a value to one position of an array
361 internal class ArrayFixupRecord : BaseFixupRecord
365 public ArrayFixupRecord (ObjectRecord objectToBeFixed, int index, ObjectRecord objectRequired): base (objectToBeFixed, objectRequired) {
369 protected override void FixupImpl (ObjectManager manager) {
370 Array array = (Array)ObjectToBeFixed.ObjectInstance;
371 array.SetValue (ObjectRequired.ObjectInstance, _index);
375 // MultiArrayFixupRecord
376 // Fixup for assigning a value to several positions of an array
378 internal class MultiArrayFixupRecord : BaseFixupRecord
382 public MultiArrayFixupRecord (ObjectRecord objectToBeFixed, int[] indices, ObjectRecord objectRequired): base (objectToBeFixed, objectRequired) {
386 protected override void FixupImpl (ObjectManager manager) {
387 ObjectToBeFixed.SetArrayValue (manager, ObjectRequired.ObjectInstance, _indices);
392 // Fixup for assigning a value to a member of an object
394 internal class FixupRecord: BaseFixupRecord
396 public MemberInfo _member;
398 public FixupRecord (ObjectRecord objectToBeFixed, MemberInfo member, ObjectRecord objectRequired): base (objectToBeFixed, objectRequired) {
402 protected override void FixupImpl (ObjectManager manager) {
403 ObjectToBeFixed.SetMemberValue (manager, _member, ObjectRequired.ObjectInstance);
407 // DelayedFixupRecord
408 // Fixup for assigning a value to a SerializationInfo of an object
410 internal class DelayedFixupRecord: BaseFixupRecord
412 public string _memberName;
414 public DelayedFixupRecord (ObjectRecord objectToBeFixed, string memberName, ObjectRecord objectRequired): base (objectToBeFixed, objectRequired) {
415 _memberName = memberName;
418 protected override void FixupImpl (ObjectManager manager) {
419 ObjectToBeFixed.SetMemberValue (manager, _memberName, ObjectRequired.ObjectInstance);
425 internal enum ObjectRecordStatus: byte { Unregistered, ReferenceUnsolved, ReferenceSolved }
427 internal class ObjectRecord
429 public ObjectRecordStatus Status = ObjectRecordStatus.Unregistered;
430 public object OriginalObject;
431 public object ObjectInstance;
432 public long ObjectID;
433 public SerializationInfo Info;
434 public long IdOfContainingObj;
435 public ISerializationSurrogate Surrogate;
436 public ISurrogateSelector SurrogateSelector;
437 public MemberInfo Member;
438 public int[] ArrayIndex;
439 public BaseFixupRecord FixupChainAsContainer;
440 public BaseFixupRecord FixupChainAsRequired;
441 public ObjectRecord Next;
443 public void SetMemberValue (ObjectManager manager, MemberInfo member, object value)
445 if (member is FieldInfo)
446 ((FieldInfo)member).SetValue (ObjectInstance, value);
447 else if (member is PropertyInfo)
448 ((PropertyInfo)member).SetValue (ObjectInstance, value, null);
449 else throw new SerializationException ("Cannot perform fixup");
453 ObjectRecord containerRecord = manager.GetObjectRecord (IdOfContainingObj);
454 if (containerRecord.IsRegistered)
455 containerRecord.SetMemberValue (manager, Member, ObjectInstance);
457 else if (ArrayIndex != null)
459 ObjectRecord containerRecord = manager.GetObjectRecord (IdOfContainingObj);
460 if (containerRecord.IsRegistered)
461 containerRecord.SetArrayValue (manager, ObjectInstance, ArrayIndex);
464 public void SetArrayValue (ObjectManager manager, object value, int[] indices)
466 ((Array)ObjectInstance).SetValue (value, indices);
469 public void SetMemberValue (ObjectManager manager, string memberName, object value)
471 if (Info == null) throw new SerializationException ("Cannot perform fixup");
472 Info.AddValue (memberName, value, value.GetType());
475 public bool IsInstanceReady
477 // Returns true if this object is ready to be assigned to a parent object.
480 if (!IsRegistered) return false;
481 if (IsUnsolvedObjectReference) return false;
483 // Embedded value objects cannot be assigned to their containers until fully completed
484 if (ObjectInstance.GetType ().IsValueType && (HasPendingFixups || Info != null))
491 public bool IsUnsolvedObjectReference
494 return Status != ObjectRecordStatus.ReferenceSolved;
498 public bool IsRegistered
501 return Status != ObjectRecordStatus.Unregistered;
505 public bool DoFixups (bool asContainer, ObjectManager manager, bool strict)
507 BaseFixupRecord prevFixup = null;
508 BaseFixupRecord fixup = asContainer ? FixupChainAsContainer : FixupChainAsRequired;
509 bool allFixed = true;
511 while (fixup != null)
513 if (fixup.DoFixup (manager, strict))
515 UnchainFixup (fixup, prevFixup, asContainer);
516 if (asContainer) fixup.ObjectRequired.RemoveFixup (fixup, false);
517 else fixup.ObjectToBeFixed.RemoveFixup (fixup, true);
525 fixup = asContainer ? fixup.NextSameContainer : fixup.NextSameRequired;
530 public void RemoveFixup (BaseFixupRecord fixupToRemove, bool asContainer)
532 BaseFixupRecord prevFixup = null;
533 BaseFixupRecord fixup = asContainer ? FixupChainAsContainer : FixupChainAsRequired;
534 while (fixup != null)
536 if (fixup == fixupToRemove)
538 UnchainFixup (fixup, prevFixup, asContainer);
542 fixup = asContainer ? fixup.NextSameContainer : fixup.NextSameRequired;
546 private void UnchainFixup (BaseFixupRecord fixup, BaseFixupRecord prevFixup, bool asContainer)
548 if (prevFixup == null) {
549 if (asContainer) FixupChainAsContainer = fixup.NextSameContainer;
550 else FixupChainAsRequired = fixup.NextSameRequired;
553 if (asContainer) prevFixup.NextSameContainer = fixup.NextSameContainer;
554 else prevFixup.NextSameRequired = fixup.NextSameRequired;
558 public void ChainFixup (BaseFixupRecord fixup, bool asContainer)
562 fixup.NextSameContainer = FixupChainAsContainer;
563 FixupChainAsContainer = fixup;
567 fixup.NextSameRequired = FixupChainAsRequired;
568 FixupChainAsRequired = fixup;
572 public bool LoadData (ObjectManager manager, ISurrogateSelector selector, StreamingContext context)
576 if (Surrogate != null) {
577 object new_obj = Surrogate.SetObjectData (ObjectInstance, Info, context, SurrogateSelector);
579 ObjectInstance = new_obj;
580 Status = ObjectRecordStatus.ReferenceSolved;
581 } else if (ObjectInstance is ISerializable) {
582 object[] pars = new object[] {Info, context};
583 ConstructorInfo con = ObjectInstance.GetType().GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof (SerializationInfo), typeof (StreamingContext) }, null );
584 if (con == null) throw new SerializationException ("The constructor to deserialize an object of type " + ObjectInstance.GetType().FullName + " was not found.");
585 con.Invoke (ObjectInstance, pars);
587 throw new SerializationException ("No surrogate selector was found for type " + ObjectInstance.GetType().FullName);
593 if (ObjectInstance is IObjectReference && Status != ObjectRecordStatus.ReferenceSolved)
596 ObjectInstance = ((IObjectReference)ObjectInstance).GetRealObject(context);
598 while (ObjectInstance is IObjectReference && n > 0) {
599 object ob = ((IObjectReference)ObjectInstance).GetRealObject (context);
600 if (ob == ObjectInstance)
606 throw new SerializationException ("The implementation of the IObjectReference interface returns too many nested references to other objects that implement IObjectReference.");
608 Status = ObjectRecordStatus.ReferenceSolved;
610 catch (NullReferenceException) {
611 // Give a second chance
618 // If this object is a value object embedded in another object, the parent
619 // object must be updated
621 ObjectRecord containerRecord = manager.GetObjectRecord (IdOfContainingObj);
622 containerRecord.SetMemberValue (manager, Member, ObjectInstance);
624 else if (ArrayIndex != null)
626 ObjectRecord containerRecord = manager.GetObjectRecord (IdOfContainingObj);
627 containerRecord.SetArrayValue (manager, ObjectInstance, ArrayIndex);
633 public bool HasPendingFixups
635 get { return FixupChainAsContainer != null; }