using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
using Pfz.Threading;
namespace Pfz.Caching
{
/// <summary>
/// Dictionary used by StatedPage. This dictionary is capable of doing it's cache.
/// It creates the Cache objects only during serialization, but it only
/// loads them again when the items are read for the first time. So, if you put many large
/// information into State, but only read one of them, only this one is brought
/// back to memory. This helps when you are paginating into the state. You
/// create many pages and save them into State, but you only read one of them
/// at a time.
/// </summary>
/// <typeparam name="T">
/// The type of the data this dictionary supports. When this typeparam is a reference
/// type, the this[] returns null when an item does not exists, and setting a value
/// to null will remove it from the dictionary. But, when value types are used, trying
/// to read a key that does not exist throws an exception, like any dictionary.
/// The return of null is done to keep compatibility with StateBag, so you
/// can simple change all your calls to ViewState[] to State[].
/// </typeparam>
[Serializable]
public sealed class CacheDictionary<T>:
IDictionary<string, T>,
ISerializable
{
#region Private fields
private Dictionary<string, T> fDictionary = new Dictionary<string, T>();
private Dictionary<string, Cache<T>> fOldDictionary = new Dictionary<string, Cache<T>>();
#endregion
#region Default constructor - needed as there is another constructor.
/// <summary>
/// Creates a new cache dictionary.
/// </summary>
public CacheDictionary()
{
}
#endregion
#region Properties
#region this[]
/// <summary>
/// Gets or sets an item in the dictionary. If you try to read an inexisting item, null is returned.
/// And, if you set an item to null, you simple remove it from the dictionary.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public T this[string key]
{
get
{
T result;
if (!fDictionary.TryGetValue(key, out result))
{
Cache<T> cache;
if (fOldDictionary.TryGetValue(key, out cache))
{
result = cache.Target;
fOldDictionary.Remove(key);
if (result != null)
fDictionary.Add(key, result);
}
}
return result;
}
set
{
if (value == null)
fDictionary.Remove(key);
else
fDictionary[key] = value;
fOldDictionary.Remove(key);
}
}
#endregion
#region Count
/// <summary>
/// Gets the number of items in this dictionary.
/// </summary>
public int Count
{
get
{
return fOldDictionary.Count + fDictionary.Count;
}
}
#endregion
#region Keys
/// <summary>
/// Gets all the keys present in this dictionary.
/// </summary>
public ICollection<string> Keys
{
get
{
if (fOldDictionary.Count == 0)
return fDictionary.Keys;
List<string> keys = fDictionary.Keys.ToList();
keys.AddRange(fOldDictionary.Keys);
return keys;
}
}
#endregion
#region Values
/// <summary>
/// Gets all the values present in this dictionary.
/// Caution when using this method, as it forces all cached values
/// to be brought to memory.
/// </summary>
public ICollection<T> Values
{
get
{
if (fOldDictionary.Count > 0)
{
foreach(var pair in fOldDictionary)
{
T target = pair.Value.Target;
if (target != null)
fDictionary.Add(pair.Key, target);
}
fOldDictionary.Clear();
}
return fDictionary.Values;
}
}
#endregion
#endregion
#region Methods
#region Clear
/// <summary>
/// Clears this dictionary.
/// </summary>
public void Clear()
{
fOldDictionary.Clear();
fDictionary.Clear();
}
#endregion
#region Add
/// <summary>
/// Adds an item to this dictionary, or throws an exception if an item
/// with the same key already exists.
/// </summary>
/// <param name="key">The key of the item.</param>
/// <param name="value">The value to add.</param>
public void Add(string key, T value)
{
if (value == null)
throw new ArgumentNullException("value");
if (fOldDictionary.ContainsKey(key))
throw new ArgumentException("The given key \"" + key + "\" already exists in the dictionary.", "key");
fDictionary.Add(key, value);
}
#endregion
#region ContainsKey
/// <summary>
/// Returns true if this dictionary contains an item with the specified key,
/// false otherwise.
/// </summary>
public bool ContainsKey(string key)
{
return fDictionary.ContainsKey(key) || fOldDictionary.ContainsKey(key);
}
#endregion
#region Remove
/// <summary>
/// Removes an item with the specified key.
/// </summary>
/// <returns>
/// Returns true if an item was removed, false if there was no item
/// with the specified key.
/// </returns>
public bool Remove(string key)
{
bool result = fDictionary.Remove(key) || fOldDictionary.Remove(key);
return result;
}
#endregion
#region TryGetValue
/// <summary>
/// Tries to get a value with the specified key.
/// </summary>
/// <param name="key">The key to find an item for.</param>
/// <param name="value">The output variable to store the value.</param>
/// <returns>True if a value with that key was found, false otherwise.</returns>
public bool TryGetValue(string key, out T value)
{
if (fDictionary.TryGetValue(key, out value))
return true;
Cache<T> cache;
if (fOldDictionary.TryGetValue(key, out cache))
{
value = cache.Target;
fOldDictionary.Remove(key);
if (value != null)
{
fDictionary.Add(key, value);
return true;
}
}
return false;
}
#endregion
#region GetEnumerator
/// <summary>
/// Returns an enumerator with all the Key/Value pairs that compose this
/// dictionary.
/// </summary>
public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
{
if (fOldDictionary.Count > 0)
{
foreach(var pair in fOldDictionary)
{
T target = pair.Value.Target;
if (target != null)
fDictionary.Add(pair.Key, target);
}
fOldDictionary.Clear();
}
return fDictionary.GetEnumerator();
}
#endregion
#endregion
#region Interfaces
#region ISerializable Members
/// <summary>
/// Serialization constructor. Creates the dictionary with the appropriate
/// serialization information.
/// </summary>
private CacheDictionary(SerializationInfo info, StreamingContext context)
{
fOldDictionary = new Dictionary<string, Cache<T>>();
foreach(var pair in info)
{
string name = pair.Name;
object value = pair.Value;
Cache<T> cache = value as Cache<T>;
if (cache != null)
fOldDictionary.Add(name, cache);
else
fDictionary.Add(name, (T)value);
}
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
List<string> names = Keys.ToList();
names.Sort();
int maxLengthToStoreDirectly = CacheManager.MaxLengthCacheDictionaryCanIncorporate;
BinaryFormatter binaryFormatter = new BinaryFormatter();
AbortSafe.Using
(
() => new MemoryStream(),
(memoryStream) =>
{
foreach(string name in names)
{
object value;
T typedValue;
if (fDictionary.TryGetValue(name, out typedValue))
{
value = typedValue;
memoryStream.SetLength(0);
binaryFormatter.Serialize(memoryStream, value);
if (memoryStream.Length > maxLengthToStoreDirectly || (typeof(T) == typeof(object) && value.GetType() == typeof(Cache<object>)))
value = Cache<T>.CreateFromSerializedBuffer(memoryStream.ToArray());
}
else
value = fOldDictionary[name];
info.AddValue(name, value);
}
}
);
}
#endregion
#region IDictionary<string, T> Members
void ICollection<KeyValuePair<string, T>>.Add(KeyValuePair<string, T> item)
{
Add(item.Key, item.Value);
}
bool ICollection<KeyValuePair<string, T>>.Contains(KeyValuePair<string, T> item)
{
T value;
if (TryGetValue(item.Key, out value))
return object.Equals(value, item.Value);
return false;
}
bool ICollection<KeyValuePair<string, T>>.IsReadOnly
{
get
{
return false;
}
}
void ICollection<KeyValuePair<string, T>>.CopyTo(KeyValuePair<string, T>[] array, int arrayIndex)
{
if (fOldDictionary.Count > 0)
{
foreach(var pair in fOldDictionary)
{
T target = pair.Value.Target;
if (target != null)
fDictionary.Add(pair.Key, target);
}
fOldDictionary.Clear();
}
IDictionary<string, T> dictionary = fDictionary;
dictionary.CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, T>>.Remove(KeyValuePair<string, T> item)
{
T value;
if (TryGetValue(item.Key, out value))
{
if (object.Equals(value, item.Value))
{
fDictionary.Remove(item.Key);
return true;
}
}
return false;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#endregion
}
}