using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Pfz.SerializationManagement.Binary.ItemSerializers;
using Pfz.SerializationManagement.Binary.Searchers;
using Pfz.Caching;
namespace Pfz.SerializationManagement.Binary
{
/// <summary>
/// This class is responsible for configuring the valid BinaryItemSerializers
/// for the BinarySerializer class, be it in a global level or in a local level.
/// </summary>
public sealed class BinarySerializationManager:
IDisposable
{
private static readonly object _lock = new object();
private static Dictionary<Type, IBinaryItemSerializer> _staticItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
private static readonly Dictionary<Type, IBinaryItemSerializer> _staticManualItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
private static readonly WeakHashSet<IBinarySerializationChangesObserver> _firstLevelChangesObservers = new WeakHashSet<IBinarySerializationChangesObserver>();
/// <summary>
/// Registers all the built-in binary serializers.
/// If you called the Pfz.DefaultConfiguration.Register() you don't need to call this method.
/// </summary>
static BinarySerializationManager()
{
GlobalRegisterSearcher(AssemblySerializerSearcher.Instance);
GlobalRegisterSearcher(DictionarySerializerSearcher.Instance);
GlobalRegisterSearcher(DoubleSerializerSearcher.Instance);
GlobalRegisterSearcher(EnumSerializerSearcher.Instance);
GlobalRegisterSearcher(EventInfoSerializerSearcher.Instance);
GlobalRegisterSearcher(MethodInfoSerializerSearcher.Instance);
GlobalRegisterSearcher(PairSerializerSearcher.Instance);
GlobalRegisterSearcher(PropertyInfoSerializerSearcher.Instance);
GlobalRegisterSearcher(ReferenceTypeArraySerializerSearcher.Instance);
GlobalRegisterSearcher(SerializableInterfaceSerializerSearcher.Instance);
GlobalRegisterSearcher(SerializableSerializerSearcher.Instance);
GlobalRegisterSearcher(TypeSerializerSearcher.Instance);
GlobalRegisterSearcher(ValueTypeArraySerializerSearcher.Instance);
GlobalRegister(BooleanArraySerializer.Instance);
GlobalRegister(BooleanSerializer.Instance);
GlobalRegister(ByteArraySerializer.Instance);
GlobalRegister(ByteSerializer.Instance);
GlobalRegister(DateTimeSerializer.Instance);
GlobalRegister(DBNullSerializer.Instance);
GlobalRegister(Int32Serializer.Instance);
GlobalRegister(Int64Serializer.Instance);
GlobalRegister(StringSerializer.Instance);
}
/// <summary>
/// Registers an item serializer that will be available to all threads by default.
/// </summary>
public static void GlobalRegister(IBinaryItemSerializer itemSerializer)
{
if (itemSerializer == null)
throw new ArgumentNullException("itemSerializer");
var forType = itemSerializer.ForType;
if (forType == null)
throw new ArgumentException("itemSerializer.ForType is null.", "itemSerializer");
lock (_lock)
{
_staticItemSerializers[forType] = itemSerializer;
_staticManualItemSerializers[forType] = itemSerializer;
}
_GlobalNotifyChange();
}
internal static readonly HashSet<Type> _globalDefaultDataTypes = new HashSet<Type>();
/// <summary>
/// This method registers that a given data-type is considered "default", so an
/// initial definition of its existence is not needed in the generated binary
/// serializations.
/// Note that registering a new default data-type or global data-type breaks
/// compatibility with previous serializations and should only be
/// used if reduced size is more important than future compatibility.
/// </summary>
public static void GlobalRegisterDefaultDataType(Type dataType)
{
if (dataType == null)
throw new ArgumentNullException("dataType");
lock (_lock)
_globalDefaultDataTypes.Add(dataType);
}
/// <summary>
/// Registers the given ItemSerializer and also makes it a default data-type.
/// </summary>
public static void GlobalRegisterDefaultItemSerializer(IBinaryItemSerializer itemSerializer)
{
GlobalRegister(itemSerializer);
GlobalRegisterDefaultDataType(itemSerializer.ForType);
}
private static IBinaryItemSerializer _TryGetGlobal(Type dataType, BinaryItemSerializerSearchArgs args)
{
lock (_lock)
{
{
IBinaryItemSerializer result;
if (_staticItemSerializers.TryGetValue(dataType, out result))
return result;
if (_staticManualItemSerializers.TryGetValue(dataType, out result))
{
_staticItemSerializers.Add(dataType, result);
return result;
}
}
var searchers = _GetOrderedSearchers(_globalSearchers, ref _orderedGlobalSearchers);
if (searchers.Length > 0)
{
args.ExecutingManager = null;
foreach (var searcher in searchers)
{
searcher.Search(args);
if (args._itemSerializerSet)
{
if (!args._usedLocalData)
_staticItemSerializers.Add(dataType, args._itemSerializer);
return args._itemSerializer;
}
}
}
}
return null;
}
private static readonly Dictionary<IBinaryItemSerializerSearcher, int> _globalSearchers = new Dictionary<IBinaryItemSerializerSearcher, int>();
private static IBinaryItemSerializerSearcher[] _orderedGlobalSearchers;
private static IBinaryItemSerializerSearcher[] _GetOrderedSearchers(Dictionary<IBinaryItemSerializerSearcher, int> searchers, ref IBinaryItemSerializerSearcher[] orderedSearchers)
{
var result = orderedSearchers;
if (result == null)
{
var query =
from
pair
in
searchers
orderby
pair.Value descending
select
pair.Key;
result = query.ToArray();
orderedSearchers = result;
}
return result;
}
/// <summary>
/// Registers a binary item serializer searcher that is valid for all threads, using
/// its default priority.
/// </summary>
public static void GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher)
{
if (searcher == null)
throw new ArgumentNullException("searcher");
int priority = searcher.DefaultPriority;
_GlobalRegisterSearcher(searcher, priority);
}
/// <summary>
/// Registers a binary item serializer searcher that is valid for all threads, using
/// the given priority.
/// </summary>
public static void GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
{
if (searcher == null)
throw new ArgumentNullException("searcher");
_GlobalRegisterSearcher(searcher, priority);
}
private static void _GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
{
lock (_lock)
{
_globalSearchers[searcher] = priority;
_orderedGlobalSearchers = null;
_GlobalClearNonManualRegistrations();
}
_GlobalNotifyChange();
}
private static void _GlobalClearNonManualRegistrations()
{
_staticItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
}
/// <summary>
/// Notifies that a global change has happened.
/// You should call this if, for some reason, a searcher may now generate
/// different results but you didn't actually register or unregister a searcher.
/// </summary>
public static void GlobalNotifyChange()
{
lock (_lock)
_GlobalClearNonManualRegistrations();
_GlobalNotifyChange();
}
private static void _GlobalNotifyChange()
{
/*var globalObservers = _allChangesObservers.ToList();
foreach (var observer in globalObservers)
observer.Changed(null);*/
var observers = _firstLevelChangesObservers.ToList();
foreach (var observer in observers)
observer.Changed();
}
private sealed class _ParentChangesObserver:
IBinarySerializationChangesObserver
{
private readonly BinarySerializationManager _manager;
internal _ParentChangesObserver(BinarySerializationManager manager)
{
_manager = manager;
}
public void Changed()
{
_manager._mustInvalidate = true;
_manager._NotifyChange();
}
}
private readonly _ParentChangesObserver _parentChangesObserver;
private readonly Dictionary<Type, IBinaryItemSerializer> _itemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
private readonly Dictionary<Type, IBinaryItemSerializer> _manualItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
/// <summary>
/// Creates a new Manager based on the actual local instance manager.
/// Note that this instance is not immediately made the thread default one
/// (you must use the UseAsLocalInstance method to do that).
/// </summary>
public BinarySerializationManager()
{
_parent = GetLocalInstance();
_parentChangesObserver = new _ParentChangesObserver(this);
_parent.RegisterChangesObserver(_parentChangesObserver);
}
/// <summary>
/// Creates a new instance based on the given parent.
/// Note that giving a parent of null is different than calling the parameterless
/// constructor. To get the same result you should use the GetLocalInstance() instance
/// as the parent.
/// So, with a null parent, if it is not isolated it will be tied to the global
/// configuration. It it is isolated, then it starts empty.
/// </summary>
public BinarySerializationManager(BinarySerializationManager parent, bool isIsolated = false)
{
if (parent != null && isIsolated)
throw new ArgumentException("A BinarySerializationManager can't have a parent and be isolated.");
_parent = parent;
_isIsolated = isIsolated;
if (parent != null)
{
_parentChangesObserver = new _ParentChangesObserver(this);
parent.RegisterChangesObserver(_parentChangesObserver);
}
else
if (!isIsolated)
{
_parentChangesObserver = new _ParentChangesObserver(this);
_firstLevelChangesObservers.Add(_parentChangesObserver);
}
}
/// <summary>
/// Stops this serialization manager from generating notifications.
/// </summary>
public void Dispose()
{
if (_parent != null)
_parent.UnregisterChangesObserver(_parentChangesObserver);
else
if (!_isIsolated)
_firstLevelChangesObservers.Remove(_parentChangesObserver);
}
private readonly BinarySerializationManager _parent;
/// <summary>
/// Gets the parent of this manager.
/// For items not configured in this manager, a request to the parent (if any)
/// is done.
/// </summary>
public BinarySerializationManager Parent
{
get
{
return _parent;
}
}
private readonly bool _isIsolated;
/// <summary>
/// Gets a value indicating if this manager is isolated.
/// By default, even when the managers don't have a parent, they are tied to the
/// global configuration. When they are isolated, they ignore even those global
/// configurations.
/// </summary>
public bool IsIsolated
{
get
{
return _isIsolated;
}
}
[ThreadStatic]
internal static BinarySerializationManager _localInstance;
/// <summary>
/// Gets the thread-specific BinarySerializationManager.
/// With it you may ask for a display name or register thread specific ones.
/// </summary>
public static BinarySerializationManager GetLocalInstance()
{
var result = _localInstance;
if (result == null)
{
result = new BinarySerializationManager(null);
_localInstance = result;
}
return result;
}
/// <summary>
/// Uses this manager as the default local instance.
/// Call it with a using clause, so the old one is restored at the end.
/// </summary>
public UseBinarySerializationManagerAsLocalInstance UseAsLocalInstance()
{
var oldInstance = GetLocalInstance();
return new UseBinarySerializationManagerAsLocalInstance(this, oldInstance);
}
/// <summary>
/// Registers an itemSerializer that is valid locally.
/// </summary>
public void Register(IBinaryItemSerializer itemSerializer)
{
if (itemSerializer == null)
throw new ArgumentNullException("itemSerializer");
var forType = itemSerializer.ForType;
if (forType == null)
throw new ArgumentException("itemSerializer.ForType is null.", "itemSerializer");
_itemSerializers[forType] = itemSerializer;
_manualItemSerializers[forType] = itemSerializer;
NotifyChange();
}
internal HashSet<Type> _defaultDataTypes = new HashSet<Type>();
/// <summary>
/// This method registers that a given data-type is considered "default", so an
/// initial definition of its existence is not needed in the generated binary
/// serializations.
/// Note that registering a new default data-type or global data-type breaks
/// compatibility with previous serializations and should only be
/// used if reduced size is more important than future compatibility.
/// </summary>
public void RegisterDefaultDataType(Type dataType)
{
if (dataType == null)
throw new ArgumentNullException("dataType");
_defaultDataTypes.Add(dataType);
}
/// <summary>
/// Registers the given itemSerializer and also makes it
/// a default one.
/// </summary>
public void RegisterDefaultItemSerializer(IBinaryItemSerializer itemSerializer)
{
Register(itemSerializer);
RegisterDefaultDataType(itemSerializer.ForType);
}
private bool _mustInvalidate;
internal IBinaryItemSerializer _TryGet(BinarySerializer sender, Type dataType, BinaryItemSerializerSearchArgs args)
{
if (_mustInvalidate)
{
_mustInvalidate = false;
_itemSerializers.Clear();
}
{
IBinaryItemSerializer result;
if (_itemSerializers.TryGetValue(dataType, out result))
return result;
if (_manualItemSerializers.TryGetValue(dataType, out result))
{
_itemSerializers[dataType] = result;
return result;
}
}
if (args == null)
args = new BinaryItemSerializerSearchArgs(sender, dataType);
var searchers = _GetOrderedSearchers(_searchers, ref _orderedSearchers);
if (searchers.Length > 0)
{
args.ExecutingManager = this;
foreach (var searcher in searchers)
{
searcher.Search(args);
if (args._itemSerializerSet)
break;
}
}
if (!args._itemSerializerSet)
{
if (_isIsolated)
return null;
if (_parent != null)
args._itemSerializer = _parent._TryGet(sender, dataType, args);
else
args._itemSerializer = _TryGetGlobal(dataType, args);
}
if (!args._usedLocalData)
_itemSerializers.Add(dataType, args._itemSerializer);
return args._itemSerializer;
}
/// <summary>
/// Creates a new Serializer bound to a given stream.
/// It is your responsability to call the stream.Close or stream.Flush.
/// Subsequent serializations/deserializations use the already found
/// serializers by index (like a compression). So, the items serialized
/// by the same serializer must be deserialized, in order, by the same
/// deserializer.
/// </summary>
public BinarySerializer CreateSerializer(Stream stream, object userContext = null)
{
if (stream == null)
throw new ArgumentNullException("stream");
return new BinarySerializer(this, stream, userContext);
}
private readonly Dictionary<IBinaryItemSerializerSearcher, int> _searchers = new Dictionary<IBinaryItemSerializerSearcher, int>();
private IBinaryItemSerializerSearcher[] _orderedSearchers;
/// <summary>
/// Registers a binary-item serialization searcher using its default priority.
/// </summary>
public void RegisterSearcher(IBinaryItemSerializerSearcher searcher)
{
if (searcher == null)
throw new ArgumentNullException("searcher");
int priority = searcher.DefaultPriority;
_RegisterSearcher(searcher, priority);
}
/// <summary>
/// Registers a binary-item serialization searcher using the given priority.
/// </summary>
public void RegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
{
if (searcher == null)
throw new ArgumentNullException("searcher");
_RegisterSearcher(searcher, priority);
}
private void _RegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
{
_searchers[searcher] = priority;
_orderedSearchers = null;
_mustInvalidate = true;
NotifyChange();
}
private readonly WeakHashSet<IBinarySerializationChangesObserver> _changesObservers = new WeakHashSet<IBinarySerializationChangesObserver>();
/// <summary>
/// Registers an observer to be notified of changes to this
/// BinarySerializationManager or any of its parent.
/// Note 1: that changes made to global configuration generate the
/// notification on the thread that made the change, independent
/// if this is a local instance of another thread (if it can be affected,
/// it will be notified).
/// Note 2: This is a weak registration and, so, if there are no other
/// references to the registered observer, it will be collected at
/// the next collection, even if that's not what you want.
/// </summary>
public void RegisterChangesObserver(IBinarySerializationChangesObserver observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
_changesObservers.Add(observer);
}
/// <summary>
/// Unregisters a previously registered change observer.
/// </summary>
public void UnregisterChangesObserver(IBinarySerializationChangesObserver observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
_changesObservers.Remove(observer);
}
/// <summary>
/// Notifies change observer that a change happened.
/// You should use this method if a searcher can now generate different
/// results but you are not actually registering or unregistering a searcher.
/// </summary>
public void NotifyChange()
{
/*var observers = _allChangesObservers.ToList();
foreach (var observer in observers)
observer.Changed(this);*/
_NotifyChange();
}
private void _NotifyChange()
{
var observers = _changesObservers.ToList();
foreach (var observer in observers)
observer.Changed();
}
}
}