// ======================================================== #undef DEBUG
namespace Kerosene.ORM.Maps
{
using Kerosene.ORM.Core;
using Kerosene.Tools;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
// ====================================================
public static partial class KMap
{
internal static ulong _LastSerialMetaLink = 0;
internal const string _MetaLinkTag = "::KMetaLink::";
}
// ==================================================== Structure and Configuration
/// <summary>
/// Represents extensions of a generic connection (or data context) in order to support maps and entities.
/// </summary>
public partial class KMetaLink : IKMetaLink, IDisposableExtended, ICloneableWithLink
{
/// <summary>
/// Locates the meta link associated with the given link, creating it if needed and requested.
/// </summary>
internal static KMetaLink Locate(IKLink link, bool create = true)
{
if (link == null) throw new ArgumentNullException("link", "Link cannot be null.");
if (link.IsDisposed)
{
if (create) throw new ObjectDisposedException(link.ToString());
return null;
}
KMetaLink metalink = null; WithExtendedInfoLock(link, () =>
{
foreach (var kvp in link.ExtendedInfo)
if (kvp.Key == KMap._MetaLinkTag) { metalink = (KMetaLink)kvp.Value; break; }
if (metalink == null && create)
{
metalink = new KMetaLink(); link.ExtendedInfo.Add(KMap._MetaLinkTag, metalink);
metalink._Link = link;
metalink._SerialId = ++KMap._LastSerialMetaLink;
metalink.EnableCollector();
}
});
return metalink;
}
/// <summary>
/// Executes the given action under a lock on the extended info repository of the given link.
/// </summary>
internal static void WithExtendedInfoLock(IKLink link, Action action)
{
lock (((ICollection)link.ExtendedInfo).SyncRoot) { action(); }
}
/// <summary>
/// Gets whether the extended info repository of the given link is currently under a lock or not.
/// </summary>
internal static bool IsExtendedInfoLocked(IKLink link)
{
return Monitor.IsEntered(((ICollection)link.ExtendedInfo).SyncRoot);
}
bool _IsDisposed = false;
IKLink _Link = null;
ulong _SerialId = 0;
System.Timers.Timer _Timer = null; // Null to avoid auto start
int _Interval = KMap.DefaultCollectorInterval; // Default value unless modified by the app
bool _InvokeGC = KMap.DefaultCollectorGCEnabled; // Default value unless modified by the app
List<IKMetaMapInternal> _MetaMaps = new List<IKMetaMapInternal>();
List<KMetaEntity> _MetaEntities = new List<KMetaEntity>();
bool _UseTransactionsWhenSubmitChanges = KMap.DefaultUseTransactionsWhenSubmitChanges;
private KMetaLink() { }
/// <summary>
/// Gets whether this instance has been disposed or not.
/// </summary>
internal bool IsDisposed
{
get { return _IsDisposed; }
}
bool IDisposableExtended.IsDisposed
{
get { return this.IsDisposed; }
}
/// <summary>
/// Disposes this instance.
/// </summary>
internal void Dispose()
{
if (!IsDisposed) { OnDispose(true); GC.SuppressFinalize(this); }
}
void IDisposable.Dispose()
{
this.Dispose();
}
~KMetaLink()
{
if (!IsDisposed) OnDispose(false);
}
protected virtual void OnDispose(bool disposing)
{
if (!IsDisposed && disposing)
{
if (_Timer != null)
{
_Timer.Stop(); _Timer.Dispose();
_Timer = null;
}
if (_MetaMaps != null) WithMetaMapsLock(() =>
{
var list = new List<IKMetaMapInternal>(_MetaMaps); foreach (var map in list) map.Dispose();
list.Clear(); list = null;
_MetaMaps.Clear();
});
if (_MetaEntities != null) WithMetaEntitiesLock(() =>
{
var list = new List<KMetaEntity>(_MetaEntities); foreach (var meta in list) meta.Clear();
list.Clear(); list = null;
_MetaEntities.Clear();
});
if (_Link != null && !_Link.IsDisposed)
{
WithExtendedInfoLock(_Link, () => { _Link.ExtendedInfo.Remove(KMap._MetaLinkTag); });
_Link = null;
}
}
_IsDisposed = true;
}
/// <summary>
/// Returns a new instance that is a copy of this original one, but associated with the new link reference
/// given.
/// </summary>
/// <param name="newlink">The link reference the new cloned instance will be associated with.</param>
internal KMetaLink Clone(IKLink newlink)
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
KMetaLink cloned = null; WithExtendedInfoLock(newlink, () =>
{
cloned = KMetaLink.Locate(newlink, create: false);
if (cloned != null) throw new NotEmptyException(string.Format(
"Link '{0}' already has a meta link associated with it.", newlink));
cloned = KMetaLink.Locate(newlink, create: true);
if (cloned == null) throw new CannotCreateException(string.Format(
"Cannot create a meta link for link '{0}'.", newlink));
WithMetaMapsLock(() => { foreach (var map in _MetaMaps) map.Clone(newlink); });
});
return cloned;
}
object ICloneableWithLink.Clone(IKLink newlink)
{
return this.Clone(newlink);
}
/// <summary>
/// Returns the string representation of this instance.
/// </summary>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
if (IsDisposed) sb.Append("disposed::[");
sb.AppendFormat("{0},{1}({2})",
SerialId,
GetType().EasyName(),
Link == null ? "-" : Link.ToString());
if (IsDisposed) sb.Append("]");
return sb.ToString();
}
/// <summary>
/// Gets the serial id assigned to this instance.
/// </summary>
public ulong SerialId
{
get { return _SerialId; }
}
/// <summary>
/// The link this meta link is associated with.
/// </summary>
public IKLink Link
{
get { return _Link; }
}
/// <summary>
/// Gets the collection of maps registered into this data context.
/// </summary>
internal List<IKMetaMapInternal> MetaMaps
{
get { return _MetaMaps; }
}
/// <summary>
/// Gets whether the store of registered maps is locked or not.
/// </summary>
internal bool IsMetaMapsLocked
{
get { return Monitor.IsEntered(((ICollection)_MetaMaps).SyncRoot); }
}
/// <summary>
/// Executes the given action with a lock on the store of registered maps.
/// </summary>
/// <param name="action">The action to execute.</param>
internal void WithMetaMapsLock(Action action)
{
var enabled = _Timer == null ? false : _Timer.Enabled; if (enabled) _Timer.Enabled = false;
lock (((ICollection)_MetaMaps).SyncRoot) { action(); }
if (enabled) _Timer.Enabled = true;
}
/// <summary>
/// Returns the map that has been registered into this instance for the given type, or null if no such map
/// can be found.
/// </summary>
/// <param name="type">The type of the entities managed by the map.</param>
public IKMetaMap Map(Type type)
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (type == null) throw new ArgumentNullException("type", "Type cannot be null.");
IKMetaMap map = null; WithMetaMapsLock(() => { map = _MetaMaps.Find(x => x.EntityType == type); });
return map;
}
/// <summary>
/// Returns the map that has been registered into this instance for the given type, or null if no such map
/// can be found.
/// </summary>
/// <typeparam name="T">The type of the entities managed by the map.</typeparam>
public KMetaMap<T> Map<T>() where T : class
{
return (KMetaMap<T>)Map(typeof(T));
}
/// <summary>
/// Gets the collection of maps that are currently registered into this link.
/// </summary>
public IEnumerable<IKMetaMap> Maps()
{
return _MetaMaps;
}
/// <summary>
/// Removes and disposes all the maps that are registered into this instance.
/// </summary>
public void ClearMaps()
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
WithMetaMapsLock(() =>
{
var list = new List<IKMetaMap>(_MetaMaps); foreach (var map in list) map.Dispose();
list.Clear(); list = null;
_MetaMaps.Clear();
});
}
/// <summary>
/// Gets the collection of entities currently managed by this data context.
/// </summary>
internal List<KMetaEntity> MetaEntities
{
get { return _MetaEntities; }
}
/// <summary>
/// Gets whether the cache of entities is currently under a lock or not.
/// </summary>
internal bool IsMetaEntitiesLocked
{
get { return Monitor.IsEntered(((ICollection)_MetaEntities).SyncRoot); }
}
/// <summary>
/// Executes the given action with a lock on the cache of entities.
/// </summary>
/// <param name="action">The action to execute.</param>
internal void WithMetaEntitiesLock(Action action)
{
var enabled = _Timer == null ? false : _Timer.Enabled; if (enabled) _Timer.Enabled = false;
lock (((ICollection)_MetaEntities).SyncRoot) { action(); }
if (enabled) _Timer.Enabled = true;
}
/// <summary>
/// Removes all the entities that may have been kept in this instance's cache.
/// </summary>
public void ClearCache()
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
WithMetaEntitiesLock(() =>
{
var list = new List<KMetaEntity>(_MetaEntities); foreach (var meta in list) meta.Clear();
list.Clear(); list = null;
_MetaEntities.Clear();
});
}
}
// ==================================================== Collector API
public partial class KMetaLink
{
/// <summary>
/// Enables the collector of this link or resumes it.
/// <para>This method is provided for debug or specialized scenarios only.</para>
/// </summary>
public void EnableCollector()
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (_Timer == null)
{
_Timer = new System.Timers.Timer();
_Timer.Elapsed += new System.Timers.ElapsedEventHandler(Collector);
}
_Timer.Interval = _Interval;
_Timer.Enabled = true;
}
/// <summary>
/// Enables the collector for this lilnk or resumes it, modifying its operational parameters.
/// <para>This method is provided for debug or specialized scenarios only.</para>
/// </summary>
/// <param name="milliseconds">The interval in milliseconds at which the collector is fired. If -1 then the
/// default value is used.</param>
/// <param name="invokeGC">Whether to force a CLR garbage collection before the collector is fired.</param>
public void EnableCollector(int milliseconds, bool invokeGC)
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (milliseconds < 0) milliseconds = KMap.DefaultCollectorInterval;
if (milliseconds < KMap.DefaultCollectorMinimumInterval) milliseconds = KMap.DefaultCollectorMinimumInterval;
_Interval = milliseconds;
_InvokeGC = invokeGC;
EnableCollector();
}
/// <summary>
/// Disables the collector of this manager.
/// <para>This method is provided for debug or specialized scenarios only.</para>
/// </summary>
public void DisableCollector()
{
if (_Timer != null) _Timer.Stop();
}
/// <summary>
/// Gets whether the collector of this manager is enabled or not.
/// <para>This provided is provided for debug or specialized scenarios only.</para>
/// </summary>
public bool CollectorEnabled
{
get { return _Timer == null ? false : _Timer.Enabled; }
}
}
// ==================================================== Collector call-back and Loop
public partial class KMetaLink
{
void Collector(object source, System.Timers.ElapsedEventArgs args)
{
if (!IsDisposed)
{
DebugHelper.Indent();
if (!IsMetaEntitiesLocked)
{
DebugHelper.WriteLine("\n- Fired collector of '{0}'...", this);
if (_InvokeGC) GC.Collect();
MetaEntitiesLoop(null);
}
else { DebugHelper.WriteLine("\n- Skipped collector of '{0}'...", this); }
DebugHelper.Unindent();
}
}
/// <summary>
/// Iterates through the collection of meta entities, releasing them if neccesary, and otherwise executing
/// the given function with each of them, if that function is not null. If the function returns true the loop
/// is aborted. If returns false the loop continues.
/// </summary>
internal void MetaEntitiesLoop(Func<KMetaEntity, bool> func = null)
{
WithMetaEntitiesLock(() =>
{
for (int i = 0, count = _MetaEntities.Count; i < count; i++)
{
var meta = _MetaEntities[i];
if (meta.State == KMetaState.Collected || meta.State == KMetaState.Detached)
{
DebugHelper.IndentWriteLine("\n- Collecting '{0}'...", meta);
_MetaEntities.RemoveAt(i); i--; count--;
meta.Clear();
DebugHelper.Unindent();
}
else if (func != null && func(meta)) break;
}
});
}
}
}
// ========================================================