/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Util;
using System;
using System.Collections.Generic;
namespace Amazon.Runtime.Internal.Util
{
///
/// a size-limited cache of key value pairs
///
/// Once the maximum size has been reached, the least recently
/// used pairs will be evicted to make room for any new items.
///
///
///
public class LruCache
where TKey : class
where TValue : class
{
private readonly object cacheLock = new object();
private Dictionary> cache;
private LruList lruList;
///
/// the maximum number of entries this LruCache will hold
/// before items begin getting evicted
///
public int MaxEntries { get; private set; }
///
/// the number of items in the cache
///
public int Count
{
get
{
lock (cacheLock)
{
return cache.Count;
}
}
}
///
/// Construct a new LruCache.
///
/// maximum number of entries before items are evicted
public LruCache(int maxEntries)
{
if (maxEntries < 1)
{
throw new ArgumentException("maxEntries must be greater than zero.");
}
MaxEntries = maxEntries;
cache = new Dictionary>();
lruList = new LruList();
}
///
/// Returns the least recently used item if it exists.
///
/// The item that is least recently used or the default value of the LruListItem class if the LRU cache is empty.
public LruListItem FindOldestItem()
{
lock (cacheLock)
{
var item = default(LruListItem);
if (lruList.Tail != null)
item = lruList.Tail;
return item;
}
}
///
/// Method to evict expired LRUListItems.
///
/// Number of seconds the LRUListItems are valid for.
public void EvictExpiredLRUListItems(int validityInSeconds)
{
lock (cacheLock)
{
while (Count != 0)
{
var item = FindOldestItem();
#pragma warning disable CS0618 // Type or member is obsolete
var timeSpan = AWSSDKUtils.CorrectedUtcNow - item.LastTouchedTimestamp;
#pragma warning restore CS0618 // Type or member is obsolete
if (timeSpan.TotalSeconds > validityInSeconds)
Evict(item.Key);
else
break;
}
}
}
///
/// Add the key/value pair to the cache, or update
/// the value if the key already exists.
///
/// If the cache is full, evicts the least recently used item.
///
///
///
public void AddOrUpdate(TKey key, TValue value)
{
lock (cacheLock)
{
LruListItem existingLruListItem;
if (cache.TryGetValue(key, out existingLruListItem))
{
// update
existingLruListItem.Value = value;
lruList.Touch(existingLruListItem);
}
else
{
// add
var newLruListItem = new LruListItem(key, value);
while (cache.Count >= MaxEntries)
{
cache.Remove(lruList.EvictOldest());
}
lruList.Add(newLruListItem);
cache.Add(key, newLruListItem);
}
}
}
///
/// Evicts a specific key out of the cache if it exists
///
/// the key to evict from the cache
public void Evict(TKey key)
{
lock (cacheLock)
{
LruListItem existingLruListItem;
if (cache.TryGetValue(key, out existingLruListItem))
{
lruList.Remove(existingLruListItem);
cache.Remove(key);
}
}
}
///
/// Try to get the value associated with the key.
///
/// the key to look up
/// the value, if the key exists
/// true if there is a value associated with the key, or false if no value is associated with the key
public bool TryGetValue(TKey key, out TValue value)
{
LruListItem existingListItem;
lock (cacheLock)
{
if (cache.TryGetValue(key, out existingListItem))
{
lruList.Touch(existingListItem);
value = existingListItem.Value;
return true;
}
else
{
value = null;
return false;
}
}
}
///
/// Try to get the value associated with the key, if it doesn't exist, use the provided factory method to
/// create a new value and tries adding it to the cache.
///
/// the key to look up
/// the factory method used in case the key is not present in the cache
/// the looked up value or the value created by the factory
public TValue GetOrAdd(TKey key, Func factory)
{
TValue value;
if (TryGetValue(key, out value))
{
return value;
}
value = factory(key);
AddOrUpdate(key, value);
return value;
}
///
/// Clear the LruCache of all entries.
///
public void Clear()
{
lock (cacheLock)
{
cache.Clear();
lruList.Clear();
}
}
}
///
/// Helper class to support LruCache.
/// Does not implement the error checking and synchronization that
/// would be necessary for it to stand alone.
///
public class LruList
{
public LruListItem Head { get; private set; }
public LruListItem Tail { get; private set; }
public void Add(LruListItem item)
{
if (Head == null)
{
Head = item;
Tail = item;
item.Previous = null;
item.Next = null;
}
else
{
Head.Previous = item;
item.Next = Head;
item.Previous = null;
Head = item;
}
#pragma warning disable CS0618 // Type or member is obsolete
item.LastTouchedTimestamp = AWSSDKUtils.CorrectedUtcNow;
#pragma warning restore CS0618 // Type or member is obsolete
}
public void Remove(LruListItem item)
{
if (Head == item || Tail == item)
{
if (Head == item)
{
Head = item.Next;
if (Head != null)
{
Head.Previous = null;
}
}
if (Tail == item)
{
Tail = item.Previous;
if (Tail != null)
{
Tail.Next = null;
}
}
}
else
{
item.Previous.Next = item.Next;
item.Next.Previous = item.Previous;
}
item.Previous = null;
item.Next = null;
}
public void Touch(LruListItem item)
{
Remove(item);
Add(item);
}
public TKey EvictOldest()
{
TKey key = default(TKey);
if (Tail != null)
{
key = Tail.Key;
Remove(Tail);
}
return key;
}
internal void Clear()
{
Head = null;
Tail = null;
}
}
///
/// Item to be stored in the LruList
/// linked list structure.
///
public class LruListItem
{
public TValue Value { get; set; }
public TKey Key { get; private set; }
internal DateTime LastTouchedTimestamp { get; set; }
public LruListItem Next { get; set; }
public LruListItem Previous { get; set; }
public LruListItem(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
}