/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Lucene.Net.Index;
namespace Lucene.Net.Util.Cache
{
///
/// Root custom cache to allow a factory to retain references to the custom
/// caches without having to be aware of the type.
///
public abstract class AbstractSegmentCache
{
///
/// Used to warm up the cache.
///
/// The reader to warm the cache for.
/// The inner key.
public abstract void Warm(IndexReader reader, string key);
}
///
/// Custom cache with two levels of keys, outer key is the IndexReader
/// with the inner key being a string, commonly a field name but can be anything.
/// Refer to the unit tests for an example implementation.
/// The type that is being cached.
///
public abstract class SegmentCache : AbstractSegmentCache
{
///
/// The cache - outer key is the reader, inner key is the field name. Value is the item desired.
///
private Dictionary> readerCache = new Dictionary>();
///
/// Lock to use when accessing the cache.
///
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
///
/// Value creation.
///
/// The reader.
/// The key to the item under the reader.
/// The value.
protected abstract T CreateValue(IndexReader reader, string key);
///
/// The number of outermost keys in the collection.
///
public int KeyCount
{
get { return this.readerCache.Count; }
}
///
/// Warm the cache - simply calls Get and ignores the return value.
///
/// The index reader to warm up.
/// The key of the item under the reader.
public override void Warm(IndexReader reader, string key)
{
this.Get(reader, key);
}
///
/// Get the item from the cache.
///
/// The IndexReader the cache is from.
/// The key of the item under the reader.
/// The item from cache.
public virtual T Get(IndexReader reader, string key)
{
WeakKey readerRef = new SegmentCache.WeakKey(reader);
Dictionary innerCache;
T retVal = default(T);
this.cacheLock.EnterReadLock();
try
{
if (readerCache.TryGetValue(readerRef, out innerCache))
{
innerCache.TryGetValue(key, out retVal);
}
}
finally
{
this.cacheLock.ExitReadLock();
}
if (retVal == null)
{
retVal = this.CreateValue(reader, key);
this.cacheLock.EnterWriteLock();
try
{
if (!readerCache.TryGetValue(readerRef, out innerCache))
{
innerCache = new Dictionary();
readerCache.Add(readerRef, innerCache);
}
if (!innerCache.ContainsKey(key))
{
innerCache[key] = retVal;
}
else
{
// another thread must have put it in while waiting for the write lock
// assumption is that the previous thread already flushed the old items
return retVal;
}
// release the old items and yank the gc'd weak references
var keys = from wr in this.readerCache.Keys where !wr.IsAlive select wr;
List keysToRemove = keys.ToList();
foreach (WeakKey wk in keysToRemove)
{
this.readerCache.Remove(wk);
}
}
finally
{
this.cacheLock.ExitWriteLock();
}
}
return retVal;
}
///
/// A weak referene wrapper for the hashtable keys. Whenever a key\value pair
/// is added to the hashtable, the key is wrapped using a WeakKey. WeakKey saves the
/// value of the original object hashcode for fast comparison.
///
internal class WeakKey : WeakReference
{
///
/// The hashcode for the target.
///
private int hashCode;
///
/// Create a new WeakKey
///
/// The object to use as the target.
internal WeakKey(object target)
: base(target)
{
this.hashCode = target.GetHashCode();
}
///
/// The hash code accessor.
///
///
public override int GetHashCode()
{
return this.hashCode;
}
///
/// Equality between keys.
///
/// The object to compare to.
/// True if they are equivalent.
public override bool Equals(object obj)
{
WeakKey other = obj as WeakKey;
if (other == null)
{
return false;
}
object a = this.Target;
object b = other.Target;
if (a == null && b == null)
{
return true;
}
else if (a == null || b == null)
{
return false;
}
else
{
return a.Equals(b);
}
}
}
}
}