Files
simulation_core/lib/Migrant/Migrant/Serializer.cs

1004 lines
40 KiB
C#

//
// Copyright (c) 2012-2024 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in the LICENSE file.
using System;
using System.Collections.Generic;
using System.IO;
using Antmicro.Migrant.Customization;
using Antmicro.Migrant.Utilities;
using Antmicro.Migrant.BultinSurrogates;
using Antmicro.Migrant.VersionTolerance;
using System.Collections.ObjectModel;
using Antmicro.Migrant.Generators;
using System.Collections;
using System.Text;
using System.Linq;
namespace Antmicro.Migrant
{
/// <summary>
/// Provides the mechanism for binary serialization and deserialization of objects.
/// </summary>
/// <remarks>
/// Please consult the general serializer documentation to find the limitations
/// and constraints which serialized objects must fullfill.
/// </remarks>
public class Serializer
{
/// <summary>
/// Initializes a new instance of the <see cref="Antmicro.Migrant.Serializer"/> class.
/// </summary>
/// <param name='settings'>
/// Serializer's settings, can be null or not given, in that case default settings are
/// used.
/// </param>
public Serializer(Settings settings = null)
{
if(settings == null)
{
settings = new Settings(); // default settings
}
this.settings = settings;
objectsForSurrogates = new SwapList();
surrogatesForObjects = new SwapList();
if(settings.SupportForISerializable)
{
ForObject<System.Runtime.Serialization.ISerializable>().SetSurrogate(x => new SurrogateForISerializable(x));
ForSurrogate<SurrogateForISerializable>().SetObject(x => x.Restore());
ForObject<Delegate>().SetSurrogate<Func<Delegate, object>>(null); //because Delegate implements ISerializable but we support it directly.
}
if(settings.SupportForIXmlSerializable)
{
ForObject<System.Xml.Serialization.IXmlSerializable>().SetSurrogate(x => new SurrogateForIXmlSerializable(x));
ForSurrogate<SurrogateForIXmlSerializable>().SetObject(x => x.Restore());
}
ForObject(typeof(Hashtable)).SetSurrogate(x => new SurrogateForHashtable((Hashtable)x));
ForSurrogate<SurrogateForHashtable>().SetObject(x => x.Restore());
ForObject(typeof(ReadOnlyCollection<>)).SetSurrogateGenericType(typeof(SurrogateForReadOnlyCollection<>));
ForSurrogate(typeof(SurrogateForReadOnlyCollection<>)).SetObject(x => ((ISurrogateRestorer)x).Restore());
ForObject(typeof(Dictionary<,>)).SetSurrogateGenericType(typeof(SurrogateForDictionary<,>));
ForSurrogate(typeof(SurrogateForDictionary<,>)).SetObject(x => ((ISurrogateRestorer)x).Restore());
ForObject(typeof(HashSet<>)).SetSurrogateGenericType(typeof(SurrogateForHashSet<>));
ForSurrogate(typeof(SurrogateForHashSet<>)).SetObject(x => ((ISurrogateRestorer)x).Restore());
ForObject(typeof(SortedList<,>)).SetSurrogateGenericType(typeof(SurrogateForSortedList<,>));
ForSurrogate(typeof(SurrogateForSortedList<,>)).SetObject(x => ((ISurrogateRestorer)x).Restore());
}
/// <summary>
/// Serializes the specified object to a given stream.
/// </summary>
/// <param name='obj'>
/// Object to serialize along with its references.
/// </param>
/// <param name='stream'>
/// Stream to which the given object should be serialized. Has to be writeable.
/// </param>
/// <param name='metadataBytes'>
/// Generic metadata describing the stream.
/// </param>
public void Serialize(object obj, Stream stream, byte[] metadataBytes = null)
{
WriteHeader(stream);
if(metadataBytes != null)
{
WriteMetadata(stream, metadataBytes); // Migrant does not allow serializing nulls
}
try
{
TouchWriter(stream);
writer.WriteObject(obj);
}
finally
{
writer.Flush();
}
serializationDone = true;
}
/// <summary>
/// Returns the open stream serializer, which can be used to do consecutive serializations
/// </summary>
/// <returns>The open stream serializer.</returns>
/// <param name="stream">Stream.</param>
/// <param name="metadataBytes">Generic metadata describing the stream.</param>
public OpenStreamSerializer ObtainOpenStreamSerializer(Stream stream, byte[] metadataBytes = null)
{
WriteHeader(stream);
if(metadataBytes != null)
{
WriteMetadata(stream, metadataBytes); // Migrant does not allow serializing nulls
}
serializationDone = true;
var result = new OpenStreamSerializer(CreateWriter(stream));
return result;
}
/// <summary>
/// Returns the open stream deserializer, which can be used to do consecutive deserializations when
/// same technique was used to serialize data.
/// </summary>
/// <returns>The open stream deserializer.</returns>
/// <param name="stream">Stream.</param>
/// <param name="metadataBytes">Stream metadata.</param>
public OpenStreamDeserializer ObtainOpenStreamDeserializer(Stream stream, out byte[] metadataBytes)
{
ThrowOnWrongResult(TryReadHeader(stream, out var preserveReferences, out var disableStamping));
long streamPosition = stream.Position;
var metadataResult = TryReadMetadata(stream, out metadataBytes);
if(metadataResult != DeserializationResult.OK)
{
stream.Seek(streamPosition, SeekOrigin.Begin);
}
if(!settings.UseBuffering)
{
stream = new PeekableStream(stream);
}
var result = new OpenStreamDeserializer(CreateReader(stream), settings, stream);
return result;
}
/// <summary>
/// Gives the ability to set callback providing object for surrogate of given type. The object will be provided instead of such
/// surrogate in the effect of deserialization.
/// </summary>
/// <returns>
/// Object letting you set the object for the given surrogate type.
/// </returns>
/// <typeparam name='TSurrogate'>
/// The type for which callback will be invoked.
/// </typeparam>
public ObjectForSurrogateSetter<TSurrogate> ForSurrogate<TSurrogate>()
{
return new ObjectForSurrogateSetter<TSurrogate>(this);
}
/// <summary>
/// Gives the ability to set callback providing object for surrogate of given type. The object will be provided instead of such
/// surrogate in the effect of deserialization.
/// </summary>
/// <returns>
/// Object letting you set the object for the given surrogate type.
/// </returns>
/// <param name="type">
/// The type for which callback will be invoked.
/// </param>
public ObjectForSurrogateSetter ForSurrogate(Type type)
{
return new ObjectForSurrogateSetter(this, type);
}
/// <summary>
/// Gives the ability to set callback providing surrogate for objects of given type. The surrogate will be serialized instead of
/// the object of that type.
/// </summary>
/// <returns>
/// Object letting you set the surrogate for the given type.
/// </returns>
/// <typeparam name='TObject'>
/// The type for which callback will be invoked.
/// </typeparam>
public SurrogateForObjectSetter<TObject> ForObject<TObject>()
{
return new SurrogateForObjectSetter<TObject>(this);
}
/// <summary>
/// Gives the ability to set callback providing surrogate for objects of given type. The surrogate will be serialized instead of
/// the object of that type.
/// </summary>
/// <returns>
/// Object letting you set the surrogate for the given type.
/// </returns>
/// <param name="type">
/// The type for which callback will be invoked.
/// </param>
public SurrogateForObjectSetter ForObject(Type type)
{
return new SurrogateForObjectSetter(this, type);
}
/// <summary>
/// Deserializes object from the specified stream.
/// </summary>
/// <param name='stream'>
/// The stream to read data from. Must be readable.
/// </param>
/// <typeparam name='T'>
/// The expected type of the deserialized object. The deserialized object must be
/// convertible to this type.
/// </typeparam>
public T Deserialize<T>(Stream stream)
{
ThrowOnWrongResult(TryDeserialize(stream, out T result));
return result;
}
/// <summary>
/// Deserializes object from the specified stream.
/// </summary>
/// <param name='stream'>
/// The stream to read data from. Must be readable.
/// </param>
/// <param name="metadataBytes">Stream metadata.</param>
/// <typeparam name='T'>
/// The expected type of the deserialized object. The deserialized object must be
/// convertible to this type.
/// </typeparam>
public T Deserialize<T>(Stream stream, out byte[] metadataBytes)
{
ThrowOnWrongResult(TryDeserialize(stream, out T result, out metadataBytes));
return result;
}
/// <summary>
/// Tries to deserialize object from specified stream.
/// </summary>
/// <returns>Operation result status.</returns>
/// <param name="stream">
/// The stream to read data from. Must be readable.
/// </param>
/// <param name="obj">Deserialized object.</param>
/// <typeparam name="T">
/// The expected type of the deserialized object. The deserialized object must be
/// convertible to this type.
/// </typeparam>
public DeserializationResult TryDeserialize<T>(Stream stream, out T obj)
{
return TryDeserialize(stream, out obj, out var _);
}
/// <summary>
/// Tries to deserialize object from specified stream.
/// </summary>
/// <returns>Operation result status.</returns>
/// <param name="stream">
/// The stream to read data from. Must be readable.
/// </param>
/// <param name="obj">Deserialized object.</param>
/// <param name="metadataBytes">Stream metadata.</param>
/// <typeparam name="T">
/// The expected type of the deserialized object. The deserialized object must be
/// convertible to this type.
/// </typeparam>
public DeserializationResult TryDeserialize<T>(Stream stream, out T obj, out byte[] metadataBytes)
{
bool unused;
bool disableStamping;
obj = default(T);
metadataBytes = null;
var headerResult = TryReadHeader(stream, out unused, out disableStamping);
if(headerResult != DeserializationResult.OK)
{
return headerResult;
}
if(disableStamping != settings.DisableTypeStamping)
{
return DeserializationResult.WrongStreamConfiguration;
}
bool readMetadata = false;
long streamPosition = stream.Position;
var metadataResult = TryReadMetadata(stream, out metadataBytes);
if(metadataResult != DeserializationResult.OK)
{
stream.Seek(streamPosition, SeekOrigin.Begin);
}
else
{
readMetadata = true;
}
TouchReader(stream);
try
{
obj = reader.ReadObject<T>();
deserializationDone = true;
return DeserializationResult.OK;
}
catch(VersionToleranceException ex)
{
LastException = ex;
return DeserializationResult.TypeStructureChanged;
}
catch(Exception ex)
{
LastException = ex;
return readMetadata ? DeserializationResult.StreamCorrupted : DeserializationResult.MetadataCorrupted;
}
finally
{
reader.Flush();
}
}
/// <summary>
/// Is invoked before serialization, once for every unique, serialized object. Provides this
/// object in its single parameter.
/// </summary>
public event Action<object> OnPreSerialization;
/// <summary>
/// Is invoked after serialization, once for every unique, serialized object. Provides this
/// object in its single parameter.
/// </summary>
public event Action<object> OnPostSerialization;
/// <summary>
/// Is invoked before deserialization, once for every unique, serialized object. Provides this
/// object in its single parameter.
/// </summary>
public event Action<object> OnPostDeserialization;
/// <summary>
/// Makes a deep copy of a given object using the serializer.
/// </summary>
/// <returns>
/// The deep copy of a given object.
/// </returns>
/// <param name='toClone'>
/// The object to make a deep copy of.
/// </param>
/// <param name='settings'>
/// Settings used for serializer which does deep clone.
/// </param>
public static T DeepClone<T>(T toClone, Settings settings = null)
{
var serializer = new Serializer(settings);
var stream = new MemoryStream();
serializer.Serialize(toClone, stream);
var position = stream.Position;
stream.Seek(0, SeekOrigin.Begin);
#if DEBUG_FORMAT
if(DumpStream)
{
using(var f = File.Open("stream.dump", FileMode.Create))
{
stream.WriteTo(f);
}
}
stream.Seek(0, SeekOrigin.Begin);
#endif
var result = serializer.Deserialize<T>(stream);
if(position != stream.Position)
{
throw new InvalidOperationException(
string.Format("Internal error in serializer: {0} bytes were written, but only {1} were read.",
position, stream.Position));
}
return result;
}
private void WriteHeader(Stream stream)
{
stream.WriteByte(Magic1);
stream.WriteByte(Magic2);
stream.WriteByte(Magic3);
stream.WriteByte(VersionNumber);
stream.WriteByte(settings.ReferencePreservation == ReferencePreservation.DoNotPreserve ? (byte)0 : (byte)1);
stream.WriteByte(settings.DisableTypeStamping ? (byte)0 : (byte)1);
}
private byte ChecksumBytes(byte[] bytes)
{
return (byte)bytes.Aggregate(0x00, (acc, b) => acc ^ b);
}
private void WriteMetadata(Stream stream, byte[] metadataBytes)
{
if(metadataBytes.Length > MaximumMetadataLength)
{
throw new ArgumentException($"Metadata should be at most {MaximumMetadataLength} bytes long.");
}
stream.WriteByte((byte)metadataBytes.Length);
stream.Write(metadataBytes, 0, metadataBytes.Length);
// Append single-byte checksum - allows for a simple check if metadata was corrupted and a way to discern old format streams from the new format
// (if the checksum doesn't match, either the metadata is corrupted or the stream is in the old format)
var checksum = ChecksumBytes(metadataBytes);
stream.WriteByte(checksum);
}
private void TouchWriter(Stream stream)
{
if(writer != null)
{
writer.ReuseWithNewStream(stream);
return;
}
writer = CreateWriter(stream);
}
private ObjectWriter CreateWriter(Stream stream)
{
WriteMethods writeMethods;
if(settings.SerializationMethod == Method.Generated)
{
writeMethods.writeMethodsProvider = new DynamicMethodProvider<WriteMethodDelegate>(t =>
{
var specialWrite = ObjectWriter.LinkSpecialWrite(t);
if(specialWrite != null)
{
return specialWrite;
}
var generator = new WriteMethodGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
writeMethods.handleNewReferenceMethodsProvider = new DynamicMethodProvider<HandleNewReferenceMethodDelegate>(t =>
{
var generator = new HandleNewReferenceMethodGenerator(t, objectsForSurrogates, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject, OnPreSerialization != null, OnPostSerialization != null);
return generator.Generate();
});
writeMethods.surrogateObjectIfNeededMethodsProvider = new DynamicMethodProvider<SurrogateObjectIfNeededDelegate>(t =>
{
var generator = new SurrogateObjectIfNeededMethodGenerator(t, surrogatesForObjects, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
writeMethods.writeReferenceMethodsProvider = new DynamicMethodProvider<WriteReferenceMethodDelegate>(t =>
{
var generator = new WriteReferenceMethodGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
writeMethods.callPostSerializationHooksMethodsProvider = new DynamicMethodProvider<CallPostSerializationHooksMethodDelegate>(t =>
{
var generator = new CallPostSerializationHooksMethodGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
}
else
{
writeMethods = GetReflectionBasedWriteMethods();
}
return new ObjectWriter(stream, writeMethods, OnPreSerialization, OnPostSerialization,
surrogatesForObjects, objectsForSurrogates, settings.TreatCollectionAsUserObject,
settings.UseBuffering, settings.DisableTypeStamping, settings.ReferencePreservation);
}
internal static WriteMethods GetReflectionBasedWriteMethods()
{
WriteMethods writeMethods;
writeMethods.writeMethodsProvider = new DynamicMethodProvider<WriteMethodDelegate>(t =>
{
var specialWrite = ObjectWriter.LinkSpecialWrite(t);
return specialWrite ?? ObjectWriter.WriteObjectUsingReflection;
});
writeMethods.handleNewReferenceMethodsProvider = new DynamicMethodProvider<HandleNewReferenceMethodDelegate>(t =>
{
return (ow, o, refId) => ow.HandleNewReference(o, refId);
});
writeMethods.surrogateObjectIfNeededMethodsProvider = new DynamicMethodProvider<SurrogateObjectIfNeededDelegate>(t =>
{
return (ow, o, refId) => ow.SurrogateObjectIfNeeded(o, refId);
});
writeMethods.writeReferenceMethodsProvider = new DynamicMethodProvider<WriteReferenceMethodDelegate>(t =>
{
return (ow, o) => ow.CheckLegalityAndWriteDeferredReference(o);
});
writeMethods.callPostSerializationHooksMethodsProvider = new DynamicMethodProvider<CallPostSerializationHooksMethodDelegate>(t =>
{
return (ow, o) => ow.CallPostSerializationHooksUsingReflection(o);
});
return writeMethods;
}
private DeserializationResult TryReadHeader(Stream stream, out bool preserveReferences, out bool disableStamping)
{
preserveReferences = false;
disableStamping = false;
// Read header
var magic1 = stream.ReadByte();
var magic2 = stream.ReadByte();
var magic3 = stream.ReadByte();
if(magic1 != Magic1 || magic2 != Magic2 || magic3 != Magic3)
{
return DeserializationResult.WrongMagic;
}
var version = stream.ReadByte();
if(version != VersionNumber)
{
return DeserializationResult.WrongVersion;
}
preserveReferences = stream.ReadByteOrThrow() != 0;
disableStamping = stream.ReadByteOrThrow() == 0;
return DeserializationResult.OK;
}
private DeserializationResult TryReadMetadata(Stream stream, out byte[] metadataBytes)
{
metadataBytes = null;
// Read number of bytes - 0 is invalid
var metadataLength = stream.ReadByteOrThrow();
if(metadataLength == 0)
{
return DeserializationResult.MetadataCorrupted;
}
// Read actual metadata
var buffer = new byte[metadataLength];
stream.Read(buffer, 0, metadataLength);
// Read checksum and verify if metadata is correct
// If it's incorrect, we could be deserializing a stream in the old format or the metadata is simply corrupted
var checksum = stream.ReadByteOrThrow();
if(ChecksumBytes(buffer) != checksum)
{
return DeserializationResult.MetadataCorrupted;
}
metadataBytes = buffer;
return DeserializationResult.OK;
}
private void ThrowOnWrongResult(DeserializationResult result)
{
switch(result)
{
case DeserializationResult.OK:
return;
case DeserializationResult.WrongMagic:
throw new InvalidOperationException("Cound not find proper magic.");
case DeserializationResult.WrongVersion:
throw new InvalidOperationException("Could not deserialize data serialized with another version of serializer.");
case DeserializationResult.WrongStreamConfiguration:
throw new InvalidOperationException("Type stamping configuration does not match.");
case DeserializationResult.StreamCorrupted:
case DeserializationResult.TypeStructureChanged:
throw LastException;
default:
throw new ArgumentOutOfRangeException();
}
}
private void TouchReader(Stream stream)
{
if(reader != null)
{
reader.ReuseWithNewStream(stream);
return;
}
reader = CreateReader(stream);
}
private ObjectReader CreateReader(Stream stream)
{
ReadMethods readMethods = new ReadMethods();
if(settings.DeserializationMethod == Method.Generated)
{
readMethods.readMethodsProvider = new DynamicMethodProvider<ReadMethodDelegate>(t =>
{
var generator = new ReadMethodGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
readMethods.touchInlinedObjectMethodsProvider = new DynamicMethodProvider<TouchInlinedObjectMethodDelegate>(t =>
{
var generator = new TouchInlinedObjectGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
readMethods.completeMethodsProvider = new DynamicMethodProvider<CompleteMethodDelegate>(t =>
{
var generator = new CompletedGenerator(t, objectsForSurrogates, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject, OnPostDeserialization != null);
return generator.Generate();
});
readMethods.createObjectMethodsProvider = new DynamicMethodProvider<CreateObjectMethodDelegate>(t =>
{
var generator = new CreateObjectGenerator(t, settings.DisableTypeStamping, settings.TreatCollectionAsUserObject);
return generator.Generate();
});
readMethods.cloneContentMehtodsProvider = new DynamicMethodProvider<CloneMethodDelegate>(t =>
{
if(t.IsValueType)
{
return ObjectReader.CloneContentUsingReflection;
}
var generator = new CloneContentMethodGenerator(t);
return generator.Generate();
});
}
else
{
readMethods = GetReflectionBasedReadMethods(settings.TreatCollectionAsUserObject);
}
return new ObjectReader(stream, readMethods, objectsForSurrogates, OnPostDeserialization,
settings.TreatCollectionAsUserObject, settings.VersionTolerance, settings.UseBuffering,
settings.DisableTypeStamping, settings.ReferencePreservation, settings.ForceStampVerification);
}
internal static ReadMethods GetReflectionBasedReadMethods(bool treatCollectionAsUserObject)
{
ReadMethods readMethods = new ReadMethods();
readMethods.readMethodsProvider = new DynamicMethodProvider<ReadMethodDelegate>(t =>
{
return ObjectReader.ReadObjectInnerUsingReflection;
});
readMethods.touchInlinedObjectMethodsProvider = new DynamicMethodProvider<TouchInlinedObjectMethodDelegate>(t =>
{
return (or, refId) => or.TryTouchInlinedObjectUsingReflection(t, refId);
});
readMethods.completeMethodsProvider = new DynamicMethodProvider<CompleteMethodDelegate>(t =>
{
return (or, refId) => or.CompletedInnerUsingReflection(refId);
});
readMethods.createObjectMethodsProvider = new DynamicMethodProvider<CreateObjectMethodDelegate>(t =>
{
return () => ObjectReader.CreateObjectUsingReflection(t, treatCollectionAsUserObject);
});
return readMethods;
}
internal static object CreateGenericSurrogateUsingReflection(Type t, object obj)
{
var finalType = t.MakeGenericType(obj.GetType().GetGenericArguments());
return Activator.CreateInstance(finalType, obj);
}
/// <summary>
/// Returns the last serializer exception.
/// </summary>
public Exception LastException { get; private set; }
/// <summary>
/// Returns the current serializer settings.
/// </summary>
public Settings Settings => settings;
private bool serializationDone;
private bool deserializationDone;
private ObjectWriter writer;
private ObjectReader reader;
private readonly Settings settings;
internal readonly SwapList surrogatesForObjects;
private readonly SwapList objectsForSurrogates;
private const byte VersionNumber = 9;
private const byte Magic1 = 0x32;
private const byte Magic2 = 0x66;
private const byte Magic3 = 0x34;
private const byte MaximumMetadataLength = 255;
#if DEBUG_FORMAT
public static readonly bool DumpStream = true;
public static readonly bool DisableVarints = true;
public static readonly bool DisableBuffering = true;
#endif
internal delegate object CreateGenericSurrogateDelegate(Type t, object o);
internal struct WriteMethods
{
public DynamicMethodProvider<CallPostSerializationHooksMethodDelegate> callPostSerializationHooksMethodsProvider;
public DynamicMethodProvider<WriteReferenceMethodDelegate> writeReferenceMethodsProvider;
public DynamicMethodProvider<SurrogateObjectIfNeededDelegate> surrogateObjectIfNeededMethodsProvider;
public DynamicMethodProvider<HandleNewReferenceMethodDelegate> handleNewReferenceMethodsProvider;
public DynamicMethodProvider<WriteMethodDelegate> writeMethodsProvider;
}
internal struct ReadMethods
{
public DynamicMethodProvider<ReadMethodDelegate> readMethodsProvider;
public DynamicMethodProvider<CompleteMethodDelegate> completeMethodsProvider;
public DynamicMethodProvider<CreateObjectMethodDelegate> createObjectMethodsProvider;
public DynamicMethodProvider<TouchInlinedObjectMethodDelegate> touchInlinedObjectMethodsProvider;
public DynamicMethodProvider<CloneMethodDelegate> cloneContentMehtodsProvider;
}
/// <summary>
/// Base class for surrogate setter.
/// </summary>
public class BaseObjectForSurrogateSetter
{
internal BaseObjectForSurrogateSetter(Serializer serializer)
{
Serializer = serializer;
}
internal void CheckLegality()
{
if(Serializer.deserializationDone)
{
throw new InvalidOperationException("Cannot set objects for surrogates after any deserialization is done.");
}
}
internal readonly Serializer Serializer;
}
/// <summary>
/// Lets you set a callback providing object for type of the surrogate given to method that provided
/// this object on a serializer that provided this object.
/// </summary>
public sealed class ObjectForSurrogateSetter<TSurrogate> : BaseObjectForSurrogateSetter
{
internal ObjectForSurrogateSetter(Serializer serializer) : base(serializer)
{
}
/// <summary>
/// Sets the callback proividing object for surrogate.
/// </summary>
/// <param name='callback'>
/// Callback proividing object for surrogate. The callback can be null, in that case surrogate of the type
/// <typeparamref name="TSurrogate" /> will be deserialized as is even if there is an object for the more
/// general type.
/// </param>
/// <typeparam name='TObject'>
/// The type of the object returned by callback.
/// </typeparam>
public void SetObject<TObject>(Func<TSurrogate, TObject> callback) where TObject : class
{
CheckLegality();
Serializer.objectsForSurrogates.AddOrReplace(typeof(TSurrogate), callback);
}
}
/// <summary>
/// Lets you set a callback providing object for type of the surrogate given to method that provided
/// this object on a serializer that provided this object.
/// </summary>
public sealed class ObjectForSurrogateSetter : BaseObjectForSurrogateSetter
{
internal ObjectForSurrogateSetter(Serializer serializer, Type type) : base(serializer)
{
this.type = type;
}
/// <summary>
/// Sets the callback proividing object for surrogate.
/// </summary>
/// <param name="callback">
/// Callback proividing object for surrogate. The callback can be null, in that case surrogate of the
/// appropriate type will be deserialized as is even if there is an object for the more general type.
/// </param>
public void SetObject(Func<object, object> callback)
{
CheckLegality();
Serializer.objectsForSurrogates.AddOrReplace(type, callback);
}
private readonly Type type;
}
/// <summary>
/// Base class for object setter.
/// </summary>
public class BaseSurrogateForObjectSetter
{
internal BaseSurrogateForObjectSetter(Serializer serializer)
{
this.Serializer = serializer;
}
internal void CheckLegality()
{
if(Serializer.serializationDone)
{
throw new InvalidOperationException("Cannot set surrogates for objects after any serialization is done.");
}
}
internal readonly Serializer Serializer;
}
/// <summary>
/// Lets you set a callback providing surrogate for type of the object given to method that provided
/// this object on a serializer that provided this object.
/// </summary>
public sealed class SurrogateForObjectSetter<TObject> : BaseSurrogateForObjectSetter
{
internal SurrogateForObjectSetter(Serializer serializer) : base(serializer)
{
}
/// <summary>
/// Sets the callback providing surrogate for object.
/// </summary>
/// <param name='callback'>
/// Callback providing surrogate for object. The callback can be null, in that case object of the type
/// <typeparamref name="TObject" /> will be serialized as is even if there is a surrogate for the more
/// general type.
/// </param>
/// <typeparam name='TSurrogate'>
/// The type of the object returned by callback.
/// </typeparam>
public void SetSurrogate<TSurrogate>(Func<TObject, TSurrogate> callback) where TSurrogate : class
{
CheckLegality();
Serializer.surrogatesForObjects.AddOrReplace(typeof(TObject), callback);
}
}
/// <summary>
/// Lets you set a callback providing surrogate for type of the object given to method that provided
/// this object on a serializer that provided this object.
/// </summary>
public sealed class SurrogateForObjectSetter : BaseSurrogateForObjectSetter
{
internal SurrogateForObjectSetter(Serializer serializer, Type type) : base(serializer)
{
this.type = type;
}
/// <summary>
/// Sets the callback providing surrogate for object.
/// </summary>
/// <param name='callback'>
/// Callback providing surrogate for object. The callback can be null, in that case object of the
/// appropriate type will be serialized as is even if there is a surrogate for the more general type.
/// </param>
public void SetSurrogate(Func<object, object> callback)
{
CheckLegality();
Serializer.surrogatesForObjects.AddOrReplace(type, callback);
}
/// <summary>
/// Sets the generic surrogate type that should be used to replace the original one.
///
/// It is required for the type to be generic, has the same amount of parameters as the original
/// one and to has a constructor accepting one parameter of the original type.
/// </summary>
/// <param name="t">Generic type.</param>
public void SetSurrogateGenericType(Type t)
{
if(!type.IsGenericType || !t.IsGenericType)
{
throw new ArgumentException("Expected generic types");
}
if(type.GetGenericArguments().Length != t.GetGenericArguments().Length)
{
throw new ArgumentException("Generic arguments count mismatch");
}
if(Serializer.settings.SerializationMethod == Method.Reflection)
{
Serializer.surrogatesForObjects.AddOrReplace(type, new Func<object, object>(x =>
{
return CreateGenericSurrogateUsingReflection(t, x);
}));
}
else
{
Serializer.surrogatesForObjects.AddGenericTemplate(type, new Func<Type, Delegate>(lt =>
{
var finalType = t.MakeGenericType(lt.GetGenericArguments());
var generator = new CreateGenericSurrogateMethodGenerator(lt, finalType);
return new Func<object, object>(o => generator.Generate()(null, o));
}));
}
}
private readonly Type type;
}
/// <summary>
/// Serializer that is attached to one stream and can do consecutive serializations that are aware of the data written
/// by previous ones.
/// </summary>
public class OpenStreamSerializer : IDisposable
{
internal OpenStreamSerializer(ObjectWriter writer)
{
this.writer = writer;
}
/// <summary>
/// Serialize the specified object.
/// </summary>
/// <param name="obj">Object.</param>
public void Serialize(object obj)
{
writer.WriteObject(obj);
}
/// <summary>
/// Flushes the buffer and necessary padding. Not necessary when buffering is not used.
/// </summary>
public void Dispose()
{
writer.Flush();
}
private readonly ObjectWriter writer;
}
/// <summary>
/// Deserializer that is attached to one stream and can do consecutive deserializations that are aware of the data written
/// by previous ones.
/// </summary>
public class OpenStreamDeserializer : IDisposable
{
internal OpenStreamDeserializer(ObjectReader reader, Settings settings, Stream stream)
{
this.reader = reader;
this.settings = settings;
this.stream = stream;
}
/// <summary>
/// Deserializes next object waiting in the stream.
/// </summary>
/// <typeparam name="T">The expected formal type of object to deserialize.</typeparam>
public T Deserialize<T>()
{
return reader.ReadObject<T>();
}
/// <summary>
/// Deserializes objects until end of the stream is reached. All objects have to be castable to T.
/// This method is only available when buffering is disabled.
/// </summary>
/// <returns>Lazy collection of deserialized objects.</returns>
/// <typeparam name="T">The expected type of object to deserialize.</typeparam>
public IEnumerable<T> DeserializeMany<T>()
{
if(settings.UseBuffering)
{
throw new NotSupportedException("DeserializeMany can be only used if buffering is disabled.");
}
var peekableStream = stream as PeekableStream;
if(peekableStream == null)
{
throw new NotSupportedException("Internal error: stream is not peekable.");
}
while(peekableStream.Peek() != -1)
{
yield return Deserialize<T>();
}
}
/// <summary>
/// Reads leftover padding. Not necessary if buffering is not used.
/// </summary>
public void Dispose()
{
reader.Flush();
}
private readonly ObjectReader reader;
private readonly Settings settings;
private readonly Stream stream;
}
}
}