using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using BLToolkit.Common;
namespace BLToolkit.Aspects
{
public delegate bool IsCacheableParameterType(Type parameterType);
///
/// http://www.bltoolkit.net/Doc/Aspects/index.htm
///
[System.Diagnostics.DebuggerStepThrough]
public class CacheAspect : Interceptor
{
#region Init
public CacheAspect()
{
_registeredAspects.Add(this);
}
private MethodInfo _methodInfo;
private int? _instanceMaxCacheTime;
private bool? _instanceIsWeak;
public override void Init(CallMethodInfo info, string configString)
{
base.Init(info, configString);
info.CacheAspect = this;
_methodInfo = info.MethodInfo;
string[] ps = configString.Split(';');
foreach (string p in ps)
{
string[] vs = p.Split('=');
if (vs.Length == 2)
{
switch (vs[0].ToLower().Trim())
{
case "maxcachetime": _instanceMaxCacheTime = int. Parse(vs[1].Trim()); break;
case "isweak": _instanceIsWeak = bool.Parse(vs[1].Trim()); break;
}
}
}
}
private static readonly IList _registeredAspects = ArrayList.Synchronized(new ArrayList());
private static IList RegisteredAspects
{
get { return _registeredAspects; }
}
public static CacheAspect GetAspect(MethodInfo methodInfo)
{
lock (RegisteredAspects.SyncRoot)
foreach (CacheAspect aspect in RegisteredAspects)
if (aspect._methodInfo == methodInfo)
return aspect;
return null;
}
#endregion
#region Overrides
protected override void BeforeCall(InterceptCallInfo info)
{
if (!IsEnabled)
return;
IDictionary cache = Cache;
lock (cache.SyncRoot)
{
CompoundValue key = GetKey(info);
CacheAspectItem item = GetItem(cache, key);
if (item != null && !item.IsExpired)
{
info.InterceptResult = InterceptResult.Return;
info.ReturnValue = item.ReturnValue;
if (item.RefValues != null)
{
ParameterInfo[] pis = info.CallMethodInfo.Parameters;
int n = 0;
for (int i = 0; i < pis.Length; i++)
if (pis[i].ParameterType.IsByRef)
info.ParameterValues[i] = item.RefValues[n++];
}
info.Cached = true;
}
else
{
info.Items["CacheKey"] = key;
}
}
}
protected override void AfterCall(InterceptCallInfo info)
{
if (!IsEnabled)
return;
IDictionary cache = Cache;
lock (cache.SyncRoot)
{
CompoundValue key = (CompoundValue)info.Items["CacheKey"];
if (key == null)
return;
int maxCacheTime = _instanceMaxCacheTime ?? MaxCacheTime;
bool isWeak = _instanceIsWeak ?? IsWeak;
CacheAspectItem item = new CacheAspectItem();
item.ReturnValue = info.ReturnValue;
item.MaxCacheTime = maxCacheTime == int.MaxValue || maxCacheTime < 0?
DateTime.MaxValue:
DateTime.Now.AddMilliseconds(maxCacheTime);
ParameterInfo[] pis = info.CallMethodInfo.Parameters;
int n = 0;
foreach (ParameterInfo pi in pis)
if (pi.ParameterType.IsByRef)
n++;
if (n > 0)
{
item.RefValues = new object[n];
n = 0;
for (int i = 0; i < pis.Length; i++)
if (pis[i].ParameterType.IsByRef)
item.RefValues[n++] = info.ParameterValues[i];
}
cache[key] = isWeak? (object)new WeakReference(item): item;
}
}
#endregion
#region Global Parameters
private static bool _isEnabled = true;
public static bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; }
}
private static int _maxCacheTime = int.MaxValue;
public static int MaxCacheTime
{
get { return _maxCacheTime; }
set { _maxCacheTime = value; }
}
private static bool _isWeak;
public static bool IsWeak
{
get { return _isWeak; }
set { _isWeak = value; }
}
#endregion
#region IsCacheableParameterType
private static IsCacheableParameterType _isCacheableParameterType =
IsCacheableParameterTypeInternal;
public static IsCacheableParameterType IsCacheableParameterType
{
get { return _isCacheableParameterType; }
set { _isCacheableParameterType = value ?? IsCacheableParameterTypeInternal; }
}
private static bool IsCacheableParameterTypeInternal(Type parameterType)
{
return parameterType.IsValueType || parameterType == typeof(string);
}
#endregion
#region Cache
private IDictionary _cache;
public IDictionary Cache
{
get { return _cache ?? (_cache = CreateCache()); }
}
protected virtual CacheAspectItem CreateCacheItem(InterceptCallInfo info)
{
return new CacheAspectItem();
}
protected virtual IDictionary CreateCache()
{
return Hashtable.Synchronized(new Hashtable());
}
protected static CompoundValue GetKey(InterceptCallInfo info)
{
ParameterInfo[] parInfo = info.CallMethodInfo.Parameters;
object[] parValues = info.ParameterValues;
object[] keyValues = new object[parValues.Length];
bool[] cacheParams = info.CallMethodInfo.CacheableParameters;
if (cacheParams == null)
{
info.CallMethodInfo.CacheableParameters = cacheParams = new bool[parInfo.Length];
for (int i = 0; i < parInfo.Length; i++)
cacheParams[i] = IsCacheableParameterType(parInfo[i].ParameterType);
}
for (int i = 0; i < parValues.Length; i++)
keyValues[i] = cacheParams[i] ? parValues[i] : null;
return new CompoundValue(keyValues);
}
protected static CacheAspectItem GetItem(IDictionary cache, CompoundValue key)
{
object obj = cache[key];
if (obj == null)
return null;
WeakReference wr = obj as WeakReference;
if (wr == null)
return (CacheAspectItem)obj;
obj = wr.Target;
if (obj != null)
return (CacheAspectItem)obj;
cache.Remove(key);
return null;
}
///
/// Clear a method call cache.
///
/// The representing cached method.
public static void ClearCache(MethodInfo methodInfo)
{
if (methodInfo == null)
throw new ArgumentNullException("methodInfo");
CacheAspect aspect = GetAspect(methodInfo);
if (aspect != null)
CleanupThread.ClearCache(aspect.Cache);
}
///
/// Clear a method call cache.
///
/// The method declaring type.
/// The method name.
/// An array of objects representing
/// the number, order, and type of the parameters for the method to get.-or-
/// An empty array of the type (for example, )
/// to get a method that takes no parameters.
public static void ClearCache(Type declaringType, string methodName, params Type[] types)
{
ClearCache(GetMethodInfo(declaringType, methodName, types));
}
public static void ClearCache(Type declaringType)
{
if (declaringType == null)
throw new ArgumentNullException("declaringType");
if (declaringType.IsAbstract)
declaringType = TypeBuilder.TypeFactory.GetType(declaringType);
lock (RegisteredAspects.SyncRoot)
foreach (CacheAspect aspect in RegisteredAspects)
if (aspect._methodInfo.DeclaringType == declaringType)
CleanupThread.ClearCache(aspect.Cache);
}
public static MethodInfo GetMethodInfo(Type declaringType, string methodName, params Type[] parameterTypes)
{
if (declaringType == null)
throw new ArgumentNullException("declaringType");
if (declaringType.IsAbstract)
declaringType = TypeBuilder.TypeFactory.GetType(declaringType);
if (parameterTypes == null)
parameterTypes = Type.EmptyTypes;
MethodInfo methodInfo = declaringType.GetMethod(
methodName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
parameterTypes,
null);
if (methodInfo == null)
{
methodInfo = declaringType.GetMethod(
methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (methodInfo == null)
throw new ArgumentException(string.Format("Method '{0}.{1}' not found.",
declaringType.FullName, methodName));
}
return methodInfo;
}
///
/// Clear all cached method calls.
///
public static void ClearCache()
{
CleanupThread.ClearCache();
}
#endregion
#region Cleanup Thread
public class CleanupThread
{
private CleanupThread() {}
internal static void Init()
{
AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
Start();
}
static void CurrentDomain_DomainUnload(object sender, EventArgs e)
{
Stop();
}
static Timer _timer;
static readonly object _syncTimer = new object();
private static void Start()
{
if (_timer == null)
lock (_syncTimer)
if (_timer == null)
{
TimeSpan interval = TimeSpan.FromSeconds(10);
_timer = new Timer(Cleanup, null, interval, interval);
}
}
private static void Stop()
{
if (_timer != null)
lock (_syncTimer)
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
private static void Cleanup(object state)
{
if (!Monitor.TryEnter(RegisteredAspects.SyncRoot, 10))
{
// The Cache is busy, skip this turn.
//
return;
}
DateTime start = DateTime.Now;
int objectsInCache = 0;
try
{
_workTimes++;
List list = new List();
foreach (CacheAspect aspect in RegisteredAspects)
{
IDictionary cache = aspect.Cache;
lock (cache.SyncRoot)
{
foreach (DictionaryEntry de in cache)
{
WeakReference wr = de.Value as WeakReference;
bool isExpired;
if (wr != null)
{
CacheAspectItem ca = wr.Target as CacheAspectItem;
isExpired = ca == null || ca.IsExpired;
}
else
{
isExpired = ((CacheAspectItem)de.Value).IsExpired;
}
if (isExpired)
list.Add(de);
}
foreach (DictionaryEntry de in list)
{
cache.Remove(de.Key);
_objectsExpired++;
}
list.Clear();
objectsInCache += cache.Count;
}
}
_objectsInCache = objectsInCache;
}
finally
{
_workTime += DateTime.Now - start;
Monitor.Exit(RegisteredAspects.SyncRoot);
}
}
private static int _workTimes;
public static int WorkTimes
{
get { return _workTimes; }
}
private static TimeSpan _workTime;
public static TimeSpan WorkTime
{
get { return _workTime; }
}
private static int _objectsExpired;
public static int ObjectsExpired
{
get { return _objectsExpired; }
}
private static int _objectsInCache;
public static int ObjectsInCache
{
get { return _objectsInCache; }
}
public static void UnregisterCache(IDictionary cache)
{
lock (RegisteredAspects.SyncRoot)
RegisteredAspects.Remove(cache);
}
public static void ClearCache(IDictionary cache)
{
lock (RegisteredAspects.SyncRoot) lock (cache.SyncRoot)
{
_objectsExpired += cache.Count;
cache.Clear();
}
}
public static void ClearCache()
{
lock (RegisteredAspects.SyncRoot)
{
foreach (CacheAspect aspect in RegisteredAspects)
{
_objectsExpired += aspect.Cache.Count;
aspect.Cache.Clear();
}
}
}
}
static CacheAspect()
{
CleanupThread.Init();
}
#endregion
}
}