// Copyright Christophe Bertrand.
/* Reminder:
* KwownTypes: lets the programmer user have type descriptors in common.
* SerializationTypeDescriptors: type descriptors in a small form, for serialization.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
#if DEBUG
using System.Diagnostics;
#endif
using UniversalSerializerLib2.TypeManagement;
using System.Runtime.CompilerServices;
namespace UniversalSerializerLib2
{
internal enum StreamingModes { AssembledStream, SetOfStreams, MultiplexStream };
/// <summary>
/// Available formatters.
/// There one formatter for each file format.
/// </summary>
public enum SerializerFormatters
{
/// <summary>
/// The default formatter. Fast and efficient. This is a custom binary format.
/// </summary>
BinarySerializationFormatter,
/// <summary>
/// A XML formatter. The XML format is based on general descriptions. It is not human-readable.
/// </summary>
XmlSerializationFormatter
#if !JSON_DISABLED
,
/// <summary>
/// A JSON formatter. The JSON format is based on general descriptions. It is not human-readable.
/// </summary>
JSONSerializationFormatter
#endif
};
// ######################################################################
/// <summary>
/// A set of streams needed to serialize fast.
/// All of them must be defined.
/// </summary>
public class SetOfStreams
{
/// <summary>
/// This stream contains the type descriptions.
/// </summary>
public readonly Stream TypesStream;
/// <summary>
/// This stream contains the values.
/// </summary>
public readonly Stream InstancesStream;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="TypesStream">Type descriptors stream.</param>
/// <param name="InstancesStream">Values stream.</param>
public SetOfStreams(
Stream TypesStream,
Stream InstancesStream)
{
if (TypesStream == null)
throw new ArgumentNullException("TypesStream");
if (InstancesStream == null)
throw new ArgumentNullException("InstancesStream");
this.TypesStream = TypesStream;
this.InstancesStream = InstancesStream;
}
}
// ######################################################################
internal abstract partial class SerializationFormatter
{
// -------------------------------------------------------------------------------
public void SerializeTypeDescriptors(
Parameters parameters)
{
throw new NotImplementedException();
}
// -------------------------------------------------------------------------------
/// <summary>
/// Serialize (another) data to the stream(s), using the same CustomParameters, keeping types in common.
/// </summary>
/// <param name="data"></param>
public void SerializeAnotherData(
object data)
{
this.SubSerialize(data);
}
// -------------------------------------------------------------------------------
/// <summary>
/// Creates a container for this object and returns the container instance and type.
/// </summary>
/// <param name="obj"></param>
/// <param name="typeManager"></param>
/// <returns></returns>
internal object CreateAContainerIfNecessary(
object obj,
ref L3TypeManager typeManager)
{
ITypeContainer container = typeManager.l2TypeManager.Container;
if (container == null)
return obj;
var obj2 = container.CreateNewContainer(obj);
typeManager = l3typeManagerCollection.GetTypeManager(obj2.GetType(), this, true, false);
return obj2;
}
// -------------------------------------------------------------------------------
// ######################################################################
internal class IndexClass
{
internal IndexClass(int value)
{
this.Value = value;
}
internal readonly int Value;
}
#region Sub-Serializer
#if !WINDOWS_PHONE && !PORTABLE
ConditionalWeakTable<object, IndexClass> ClassInstanceIndexes;
#else
Dictionary<object, IndexClass> ClassInstanceIndexes; // TODO: replace by something quicker.
#endif
int ClassInstancesCount;
/// <summary>
/// Serialize (another) data, using the same parameters, keeping types in common.
/// </summary>
/// <param name="data"></param>
internal void SubSerialize(
object data
)
{
#if !WINDOWS_PHONE && !PORTABLE
this.ClassInstanceIndexes = new ConditionalWeakTable<object, IndexClass>();
#else
this.ClassInstanceIndexes = new Dictionary<object, IndexClass>(); // TODO: replace by something quicker.
#endif
this.ClassInstancesCount = 0;
this.parameters.CustomFormatter.StartTree();
// Serialize main data:
{
Type t = data != null ? data.GetType() : typeof(object);
var tm = this.l3typeManagerCollection.GetTypeManager(t, this, true, false);
this.AddAnObject(
ref this.parameters.CustomFormatter.channelInfos[(int)ChannelNumber.InstancesChannel], // The main object is added as an instance, even when it s structure.
data,
null,
tm.TypeIndex, // Type is done for the root object.
tm,
tm.l2TypeManager.IsClass, // structures are not placed at root because that would create an instance element <s1>, then during deserialization that would imply it is an instance (a class) but it is not.
true);
}
// serialize the type descriptors:
if (parameters.SerializeTypeDescriptors && parameters.TheStreamingMode == StreamingModes.AssembledStream)
{
AddACollection(
ref this.parameters.CustomFormatter.channelInfos[(int)ChannelNumber.TypeDescriptorsChannel],
this.l3typeManagerCollection.GetSerializationTypeDescriptors(),
this.l3typeManagerCollection.GetTypeManager(typeof(SerializationTypeDescriptorCollection), null, true, false)
);
}
this.parameters.CustomFormatter.FinalizeTreeToStream();
}
// ---------------------------------------------
void AddACollection(
ref ChannelInfos channelInfos,
IEnumerable iEnumerable,
L3TypeManager collectionTypeManager,
string NameToWrite = null)
{
if (collectionTypeManager.l2TypeManager.L1TypeManager.type.IsArray)
{
Array array = iEnumerable as Array;
if (array.Rank != 1)
throw new Exception("Not a unidimensional array"); // TODO: create a Container for complex arrays (not only multidimensional, see Array.GetLowerBound() too).
}
Type collectionType = iEnumerable.GetType();
L3TypeManager typeManager =
collectionTypeManager.l2TypeManager.L1TypeManager.type == collectionType ?
collectionTypeManager
: this.l3typeManagerCollection.GetTypeManager(collectionType, this, true, false);
int collectionTypeNumber =
typeManager.TypeIndex;
if (iEnumerable.IsEmpty())
{
// Optimisation: we only write an empty object as a collection.
this.ChannelAddNull(
ref channelInfos
);
return;
}
Type CommonItemType = // can be null.
typeManager.CollectionItemsTypeManager != null ?
typeManager.CollectionItemsTypeManager.l2TypeManager.L1TypeManager.type
: null;
this.ChannelEnterCollection(
ref channelInfos
#if DEBUG
, NameToWrite
#endif
#if DEBUG
, collectionType
#endif
);
var CommonItemTM = typeManager.CollectionItemsTypeManager;
int? CommonItemTypeNumber =
CommonItemTM != null ?
CommonItemTM.TypeIndex
: (int?)null;
if (CommonItemTM == null || CommonItemTM.l2TypeManager.IsClass)
{
// For not-sealed classes:
// item of foreach can not be referenced. Therefore we enumerate manually:
var enumerator = iEnumerable.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
if (item != null)
{
Type itemType = item.GetType();
L3TypeManager itemTm =
itemType != CommonItemType ?
this.l3typeManagerCollection.GetTypeManager(item.GetType(), this, true, false) // Items can have different types, even if all items inherit the same type.
: CommonItemTM;
int ItemTypeNumber = itemTm.TypeIndex;
bool WriteTypeNumber = (ItemTypeNumber != CommonItemTypeNumber); // We write the type index only if it is different from the common type index.
this.AddAnObject(ref channelInfos, item, null, ItemTypeNumber, itemTm, false, WriteTypeNumber);
}
else
this.ChannelAddNull(
ref channelInfos
);
}
}
else // structures or sealed classes can not inherit therefore can be serialized quicker:
{
#if !DEBUG
if (CommonItemTypeNumber != null)
{
// Using a generic IEnumerable is a bit quicker (11 % on .NET 4.5).
var mi2 = gfAddAllStructs.MakeGenericMethod(CommonItemType);
mi2.Invoke(this, new object[] {
iEnumerable, channelInfos, CommonItemTypeNumber.Value, CommonItemTM
});
}
else
#endif
{
// The normal algorithm makes debugging easier.
// item of foreach can not be referenced. Therefore we enumerate manually:
var enumerator = iEnumerable.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
this.AddAnObject(ref channelInfos, item, null, CommonItemTypeNumber.Value, CommonItemTM, false, false);
}
}
}
this.ChannelExitCollection(ref channelInfos);
}
#if !DEBUG
#region Serialize items of a struct enumerable
System.Reflection.MethodInfo gfAddAllStructs;
delegate void delAddAllStructs<T>(IEnumerable<T> list, ref ChannelInfos channelInfos, int TypeNumber, L3TypeManager tm);
void addAllStructs<T>(IEnumerable<T> list, ref ChannelInfos channelInfos, int TypeNumber, L3TypeManager tm)
{
T[] array = list as T[];
if (array != null)
{
int size = array.Length;
for (int i = 0; i < size; i++)
this.AddAnObject(ref channelInfos, array[i], null, TypeNumber, tm, false, false);
}
else
{
var enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
this.AddAnObject(ref channelInfos, item, null, TypeNumber, tm, false, false);
}
}
}
#endregion Serialize items of a struct enumerable
#endif
// ---------------------------------------------
void AddADictionary(
ref ChannelInfos channelInfos,
IDictionary AsIDictionary,
L3TypeManager typeManager
)
{
int dictionaryTypeNumber =
typeManager.TypeIndex;
if (AsIDictionary.Count == 0)
{
// Optimisation: we only write an empty object as a collection.
this.ChannelAddNull(
ref channelInfos
);
return;
}
L3TypeManager CommonKeyTM = typeManager.DictionaryKeysTypeManager; // can be null.
L3TypeManager CommonValueTM = typeManager.DictionaryValuesTypeManager; // can be null.
int? CommonKeyTypeNumber =
CommonKeyTM != null ?
CommonKeyTM.TypeIndex
: (int?)null;
int? CommonValueTypeNumber =
CommonValueTM != null ?
CommonValueTM.TypeIndex
: (int?)null;
this.ChannelEnterDictionary(ref channelInfos);
// TODO: from here, separate generic dictionaries from object dict.
Type CommonKeyTMType =
CommonKeyTM != null ? CommonKeyTM.l2TypeManager.L1TypeManager.type : null;
Type CommonValueTMType =
CommonValueTM != null ? CommonValueTM.l2TypeManager.L1TypeManager.type : null;
foreach (object _item in AsIDictionary)
{
object Key, Value;
if (_item is DictionaryEntry)
{
DictionaryEntry item = (DictionaryEntry)_item;
Key = item.Key;
Value = item.Value;
}
else
if (_item is KeyValuePair<object, object>)
{
KeyValuePair<object, object> item = (KeyValuePair<object, object>)_item;
Key = item.Key;
Value = item.Value;
}
else
throw new Exception();
// We simply write the key and the value, with no decoration.
if (Key != null)
{
Type itemKeyType = Key.GetType();
L3TypeManager itemKeyTm =
itemKeyType != CommonKeyTMType ?
this.l3typeManagerCollection.GetTypeManager(itemKeyType, this, true, false) // Items can have different types, even if all items inherit the same type.
: CommonKeyTM;
int ItemKeyTypeNumber = itemKeyTm.TypeIndex;
bool WriteTypeNumber = (ItemKeyTypeNumber != CommonKeyTypeNumber); // We write the type index only if it is different from the common type index.
this.AddAnObject(ref channelInfos, Key, null, ItemKeyTypeNumber, itemKeyTm, false, WriteTypeNumber);
}
else
// We add an empty element. If not, the collection count would be wrong.
this.ChannelAddNull(
ref channelInfos
);
if (Value != null)
{
Type itemValueType = Value.GetType();
L3TypeManager itemValueTm =
itemValueType != CommonValueTMType ?
this.l3typeManagerCollection.GetTypeManager(itemValueType, this, true, false) // Items can have different types, even if all items inherit the same type.
: CommonValueTM;
int ItemValueTypeNumber = itemValueTm.TypeIndex;
bool WriteTypeNumber = (ItemValueTypeNumber != CommonValueTypeNumber); // We write the type index only if it is different from the common type index.
this.AddAnObject(ref channelInfos, Value, null, ItemValueTypeNumber, itemValueTm, false, WriteTypeNumber);
}
else
// We add an empty element. If not, the collection count would be wrong.
this.ChannelAddNull(
ref channelInfos
);
}
this.ChannelExitDictionary(ref channelInfos);
}
/* TODO: later: void WriteGenericDictPairs<Tkey, TValue>(IDictionary<Tkey, TValue> dict)
{
}*/
// ---------------------------------------------
[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
internal void AddAnObject(
ref ChannelInfos channelInfos, object obj, string NameToWrite, int TypeNumber, L3TypeManager tm, bool AtRoot, bool WriteType)
{
#if DEBUG
if (tm.TypeIndex == (int)TypeCode.Object && obj != null)
Debugger.Break();
#endif
bool AddSectionMark = AtRoot && this.parameters.TheStreamingMode == StreamingModes.MultiplexStream;
if (AddSectionMark)
this.ChannelEnterChannelSection(ref channelInfos);
WriteType |= AtRoot && channelInfos.ChannelNumber == SerializationFormatter.ChannelNumber.InstancesChannel;
if (obj == null)
this.ChannelAddNull(
ref channelInfos
);
else
if (tm.l2TypeManager.IsAPrimitiveType)
this.AddPrimitiveElementOrReference(
ref channelInfos, obj,
#if DEBUG
NameToWrite,
tm.l2TypeManager.L1TypeManager.type,
#endif
TypeNumber,
null,
WriteType);
else
this.AddAComplexObject(ref channelInfos, obj,
#if DEBUG
NameToWrite,
#endif
TypeNumber, tm, WriteType);
if (AddSectionMark)
this.ChannelExitChannelSection(ref channelInfos);
}
// -------------------------------------------------
[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
void AddPrimitiveElementOrReference(
ref ChannelInfos channelInfos,
object Value,
#if DEBUG
string debug_Name,
Type debug_type,
#endif
int TypeNumber,
int? ReferenceToInstanceNumber,
bool ShouldWriteType)
{
#if DEBUG
if (Value == null)
throw new ArgumentNullException();
#endif
#region Manages instances
if (this.parameters.customModifiers.DoNotDuplicateStrings // only strings if user accepts.
&& Value.GetType() == typeof(string)
&& channelInfos.ChannelNumber == ChannelNumber.InstancesChannel)
{
IndexClass index;
if (!this.ClassInstanceIndexes.TryGetValue(Value, out index))
{
index = new IndexClass(this.AddObjToKnownInstances(Value));
// serialize the object at the root of the Instances branch:
this.ChannelAddPrimitiveElementToRoot(
ref channelInfos,
Value,
#if DEBUG
debug_Name,
#endif
TypeNumber, // For an instance, the type is always wrote.
unchecked((TypeCode)TypeNumber)
#if DEBUG
, this.l3typeManagerCollection.GetTypeManager(TypeNumber).l2TypeManager.L1TypeManager.type
#endif
);
}
// Replace instance by a reference:
this._ChannelAddReference(
ref channelInfos,
#if DEBUG
debug_Name,
#endif
index.Value
#if DEBUG
, debug_type
#endif
);
return;
}
#endregion Manages instances
this._ChannelAddPrimitiveElement(
ref channelInfos,
Value,
#if DEBUG
debug_Name,
#endif
ShouldWriteType ? TypeNumber : (int?)null,
unchecked((TypeCode)TypeNumber)
#if DEBUG
, debug_type
#endif
);
}
// -------------------------------------------------
int AddObjToKnownInstances(object obj)
{
int index = this.ClassInstancesCount++;
this.ClassInstanceIndexes.Add(obj, new IndexClass(index));
return index;
}
// -------------------------------------------------
[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
void AddAComplexObject(ref ChannelInfos channelInfos, object obj,
#if DEBUG
string NameToWrite,
#endif
int TypeNumber, L3TypeManager tm, bool WriteType)
{
#region Manages instances
bool typeToBeSerializedIsAClass =
tm.l2TypeManager.Container != null ?
true
: tm.l2TypeManager.IsClass;
if (typeToBeSerializedIsAClass && channelInfos.ChannelNumber != ChannelNumber.TypeDescriptorsChannel)
{
IndexClass index;
if (!this.ClassInstanceIndexes.TryGetValue(obj, out index))
{
index = new IndexClass(this.AddObjToKnownInstances(obj));
// serialize the object at the root of the Instances branch:
this.AddAComplexObject_NoInstanceTest(
ref this.channelInfos[(int)ChannelNumber.InstancesChannel],
obj,
#if DEBUG
NameToWrite,
#endif
tm, true,
true); // For an instance, the type is always wrote.
}
// Replace instance by a reference:
this._ChannelAddReference(
ref channelInfos,
#if DEBUG
NameToWrite,
#endif
index.Value
#if DEBUG
, this.l3typeManagerCollection.GetTypeManager(TypeNumber).l2TypeManager.L1TypeManager.type
#endif
);
return;
}
#endregion Manages instances
// We serialize the complex object:
AddAComplexObject_NoInstanceTest(ref channelInfos, obj,
#if DEBUG
NameToWrite,
#endif
tm, false, WriteType);
}
// -------------------------------------------------
[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
void AddAComplexObject_NoInstanceTest(
ref ChannelInfos channelInfos, object obj,
#if DEBUG
string NameToWrite,
#endif
L3TypeManager typeManager, bool AtRoot, bool WriteType)
{
obj = this.CreateAContainerIfNecessary(obj, ref typeManager);
IDictionary AsIDictionary = null;
bool AddSectionMark = AtRoot && this.parameters.TheStreamingMode == StreamingModes.MultiplexStream;
if (AddSectionMark)
this.ChannelEnterChannelSection(ref channelInfos);
long? NumberOfElements = null;
if (typeManager.l2TypeManager.L1TypeManager.type.IsArray)
{
Array array = obj as Array;
if (array.Rank != 1)
throw new Exception("Not a unidimensional array"); // TODO: create a Container for complex arrays (not only multidimensional, see Array.GetLowerBound() too).
NumberOfElements =
#if SILVERLIGHT || PORTABLE
array.Length;
#else
array.GetLongLength(0);
#endif
}
else
if (typeManager.l2TypeManager.L1TypeManager.IsAnObjectIEnumerable)
NumberOfElements = (obj as IEnumerable).GetCount();
else
if (typeManager.l2TypeManager.L1TypeManager.IsADictionary)
{
AsIDictionary = obj as IDictionary;
if (AsIDictionary != null)
NumberOfElements = AsIDictionary.Count;
else
{
AsIDictionary = Tools.GenericIDictionaryBoxer<int, int>.CreateFromGenericIDictionary(obj, typeManager);
if (AsIDictionary != null)
NumberOfElements = AsIDictionary.Count;
else
throw new Exception(); // error.
}
}
this.ChannelEnterSubBranch(
ref channelInfos,
NumberOfElements,
#if DEBUG
NameToWrite,
#endif
WriteType ? (int?)typeManager.TypeIndex : null,
null
#if DEBUG
, typeManager.l2TypeManager.L1TypeManager.type
#endif
, typeManager.l2TypeManager.L1TypeManager.IsStructure
);
{
#region serialize the fields.
if (typeManager.SelectedFieldTypeManagers != null)
for (int ifi = 0; ifi < typeManager.SelectedFieldTypeManagers.Length; ifi++)
{
object fieldValue =
typeManager.l2TypeManager.SelectedFieldGetters[ifi](obj);
Type FieldType = typeManager.l2TypeManager.SelectedFields[ifi].FieldType;
Type t = fieldValue != null ? fieldValue.GetType() : FieldType;
L3TypeManager _tm;
if (FieldType != t)
{
#if DEBUG
if (!t.Is(FieldType)) // TODO: optimize this test and let it even in relase compilation.
if (!FieldType.Is(typeof(Nullable<>)))
throw new Exception();
#endif
_tm = this.l3typeManagerCollection.GetTypeManager(t, this, true, false);
}
else
{
_tm = typeManager.SelectedFieldTypeManagers[ifi];
if (_tm == null)
{
_tm =
typeManager.SelectedFieldTypeManagers[ifi] = // updates the TypeManager.
this.l3typeManagerCollection.GetTypeManager(FieldType, this, true, false);
typeManager.l2TypeManager.SelectedFieldTypeManagers[ifi] = _tm.l2TypeManager;
}
}
bool WriteTypeNumber = FieldType != t;
this.AddAnObject(ref channelInfos, fieldValue,
null,
_tm.TypeIndex,
_tm, false,
WriteTypeNumber
);
}
#endregion serialize the fields.
#region serialize the properties.
if (typeManager.SelectedPropertyTypeManagers != null)
// serialize the properties.
for (int ipi = 0; ipi < typeManager.SelectedPropertyTypeManagers.Length; ipi++)
{
Type propertyType = typeManager.l2TypeManager.SelectedProperties[ipi].PropertyType;
object propertyValue =
typeManager.l2TypeManager.SelectedPropertyGetters[ipi](obj);
Type t = propertyValue != null ? propertyValue.GetType() : propertyType;
L3TypeManager _tm;
if (propertyType != t)
{
#if DEBUG
if (!t.Is(propertyType)) // TODO: optimize this test and let it even in relase compilation.
if (!propertyType.Is(typeof(Nullable<>)))
throw new Exception();
#endif
_tm = this.l3typeManagerCollection.GetTypeManager(t, this, true, false);
}
else
{
_tm = typeManager.SelectedPropertyTypeManagers[ipi];
if (_tm == null)
{
_tm =
typeManager.SelectedPropertyTypeManagers[ipi] = // updates the TypeManager.
this.l3typeManagerCollection.GetTypeManager(propertyType, this, true, false);
typeManager.l2TypeManager.SelectedPropertyTypeManagers[ipi] = _tm.l2TypeManager;
}
}
bool WriteTypeNumber = propertyType != t;
this.AddAnObject(ref channelInfos, propertyValue,
null,
_tm.TypeIndex,
_tm, false,
WriteTypeNumber
);
}
#endregion serialize the properties.
// serialize an inner dictionary:
if (typeManager.l2TypeManager.L1TypeManager.IsADictionary)
{
this.AddADictionary(
ref channelInfos, AsIDictionary, typeManager
);
}
// serialize an inner collection:
if (typeManager.l2TypeManager.L1TypeManager.IsAnObjectIEnumerable)
{
this.AddACollection(ref channelInfos, obj as IEnumerable, typeManager
);
}
}
this.ChannelExitSubBranch(ref channelInfos);
if (AddSectionMark)
this.ChannelExitChannelSection(ref channelInfos);
}
// -------------------------------------------------
// -------------------------------------------------
// -------------------------------------------------
class IndexedTypeManager
{
internal readonly int Index;
internal readonly L2TypeManager typeManager;
internal IndexedTypeManager(int Index, L2TypeManager typeManager)
{
this.Index = Index;
this.typeManager = typeManager;
}
}
// -------------------------------------------------------
// --------------
#endregion Sub-Serializer
}
// ######################################################################
// ######################################################################
}