using System;
using System.Collections.Generic;
using System.Reflection;
namespace ConfigurableConvertTest
{
/// <summary>
/// Class responsible for being the single entry point for all data-type conversions.
/// You can register global (AppDomain global) and local (per thread) converters here.
/// </summary>
public sealed class Converters:
IDisposable
{
#region Global
private static readonly Dictionary<KeyValuePair<Type, Type>, Delegate> _globalConvertersDictionary = new Dictionary<KeyValuePair<Type, Type>, Delegate>();
/// <summary>
/// Registers a converter at AppDomain level, available for all threads that do
/// not register their own specific converter for the same input/output types.
/// </summary>
public static void GlobalRegister<TInput, TOutput>(Converter<TInput, TOutput> converter)
{
if (converter == null)
throw new ArgumentNullException("converter");
var key = new KeyValuePair<Type, Type>(typeof(TInput), typeof(TOutput));
lock(_globalConvertersDictionary)
_globalConvertersDictionary[key] = converter;
}
private static Delegate _TryGetGlobalConverter(Converters sender, KeyValuePair<Type, Type> key, SearchingConverterEventArgs args)
{
lock(_globalConvertersDictionary)
{
Delegate result;
if (_globalConvertersDictionary.TryGetValue(key, out result))
return result;
var searching = GlobalSearching;
if (searching != null)
{
if (args == null)
args = new SearchingConverterEventArgs(sender, key.Key, key.Value);
args.IsGlobal = true;
var invocationList = searching.GetInvocationList();
foreach(SearchingConverterEventHandler handler in invocationList)
{
handler(args);
result = args.Converter;
if (result != null)
{
if (args.IsGlobal)
_globalConvertersDictionary.Add(key, result);
return result;
}
}
}
}
return null;
}
/// <summary>
/// Event invoked by any thread that needs a converter if one was not found
/// locally already, so you have a last chance to register a converter.
/// Note: The sender is null for this event.
/// </summary>
public static event SearchingConverterEventHandler GlobalSearching;
#endregion
#region Local
private readonly Converters _old;
/// <summary>
/// Creates a new Converters class that immediately becomes the actual one.
/// Use it with a using clause, so the previous local converters is restored at the end.
/// </summary>
/// <param name="mode">The mode in which this converters instance will relate to other converters.</param>
public Converters(ConvertersMode mode=ConvertersMode.BasedOnPreviousLocal)
{
// The old should call the LocalInstance to create the default one if needed.
_old = LocalInstance;
_mode = mode;
_threadConverters = this;
}
private Converters(Converters old)
{
_mode = ConvertersMode.BasedOnGlobal;
}
/// <summary>
/// For user-created local converters, it is very important to Dispose it at the
/// end to restore previous configurations.
/// </summary>
public void Dispose()
{
if (_old == null)
throw new InvalidOperationException("The default Converters context of a thread can't be disposed.");
var actual = _threadConverters;
while(actual != this)
{
if (actual == null)
throw new InvalidOperationException("Only the active Converters context can be disposed.");
actual = actual._old;
}
_threadConverters = _old;
}
private readonly ConvertersMode _mode;
/// <summary>
/// Gets the Mode in which the actual Converters instance relates to previous
/// local converters (if any) and the global Converters.
/// </summary>
public ConvertersMode Mode
{
get
{
return _mode;
}
}
[ThreadStatic]
private static Converters _threadConverters;
/// <summary>
/// Gets the thread-specific converters.
/// With it you may ask for a converter or register thread specific ones.
/// </summary>
public static Converters LocalInstance
{
get
{
var result = _threadConverters;
if (result == null)
{
result = new Converters(null);
_threadConverters = result;
}
return result;
}
}
private readonly Dictionary<KeyValuePair<Type, Type>, Delegate> _convertersDictionary = new Dictionary<KeyValuePair<Type, Type>, Delegate>();
/// <summary>
/// Registers a converter for the given input/output types for the actual thread.
/// If you want to register globally, use the static GlobalRegister method.
/// </summary>
public void Register<TInput, TOutput>(Converter<TInput, TOutput> converter)
{
if (converter == null)
throw new ArgumentNullException("converter");
var key = new KeyValuePair<Type, Type>(typeof(TInput), typeof(TOutput));
_convertersDictionary[key] = converter;
}
/// <summary>
/// Tries to get a converter for the given input/output types.
/// It will first look for thread specific ones and, if none is found, will look
/// for global ones. This method will return null if none is found.
/// </summary>
public Converter<TInput, TOutput> TryGet<TInput, TOutput>()
{
var result = TryGet(typeof(TInput), typeof(TOutput));
var typedResult = (Converter<TInput, TOutput>)(result);
return typedResult;
}
/// <summary>
/// Tries to get a converter for the given input/output types.
/// It will first look for thread specific ones and, if none is found, will look
/// for global ones. This method throws an InvalidOperationException if none is found.
/// </summary>
public Converter<TInput, TOutput> Get<TInput, TOutput>()
{
var result = TryGet<TInput, TOutput>();
if (result == null)
throw new InvalidOperationException("There is no converter registered for the given types.");
return result;
}
/// <summary>
/// Tries to get the delegate to convert from the given input-type to the
/// given output type. This method returns the delegate untyped, but
/// it is a valid Converter<inputType, outputType> delegate.
/// If you need to invoke it with the parameters cast as object, use
/// the CastedTryGet method.
/// </summary>
public Delegate TryGet(Type inputType, Type outputType)
{
if (inputType == null)
throw new ArgumentNullException("inputType");
if (outputType == null)
throw new ArgumentNullException("outputType");
var key = new KeyValuePair<Type, Type>(inputType, outputType);
return _TryGet(this, key, null);
}
private Delegate _TryGet(Converters sender, KeyValuePair<Type, Type> key, SearchingConverterEventArgs args)
{
Delegate result;
if (!_convertersDictionary.TryGetValue(key, out result))
{
var searching = Searching;
if (searching != null)
{
args = new SearchingConverterEventArgs(sender, key.Key, key.Value);
var invocationList = searching.GetInvocationList();
foreach (SearchingConverterEventHandler handler in invocationList)
{
handler(args);
result = args.Converter;
if (result != null)
break;
}
}
if (result == null)
{
switch (_mode)
{
case ConvertersMode.BasedOnGlobal:
result = _TryGetGlobalConverter(sender, key, args);
if (result == null)
return null;
break;
case ConvertersMode.BasedOnPreviousLocal:
result = _old._TryGet(sender, key, args);
if (result == null)
return null;
break;
default:
return null;
}
}
if (sender == this)
_convertersDictionary.Add(key, result);
}
return result;
}
/// <summary>
/// Gets the delegate to convert from the input type to the
/// output type or throws an exception if none is found.
/// If you don't plan to cast it to the generic Converter delegate
/// but want to invoke it, see the CastedGet method.
/// </summary>
public Delegate Get(Type inputType, Type outputType)
{
var result = TryGet(inputType, outputType);
if (result == null)
throw new InvalidOperationException("There is no converter registered for the given types.");
return result;
}
/// <summary>
/// Tries to get a converter for the generic input/output types, but returns
/// it as an untyped object/object converter.
/// Returns null if a converter is not found.
/// </summary>
public Converter<object, object> CastedTryGet<TInput, TOutput>()
{
var typedResult = TryGet<TInput, TOutput>();
if (typedResult == null)
return null;
return new Converter<object, object>((input) => typedResult((TInput)input));
}
/// <summary>
/// Gets a converter for the generic input/output types, but returns
/// it as an untyped object/object converter.
/// Throws an exception if a converter is not found.
/// </summary>
public Converter<object, object> CastedGet<TInput, TOutput>()
{
var result = CastedTryGet<TInput, TOutput>();
if (result == null)
throw new InvalidOperationException("There is no converter registered for the given types.");
return result;
}
private static readonly MethodInfo _castedTryGetGenericMethod = typeof(Converters).GetMethod("CastedTryGet", Type.EmptyTypes);
private readonly Dictionary<KeyValuePair<Type, Type>, Converter<object, object>> _castedConverters = new Dictionary<KeyValuePair<Type, Type>, Converter<object, object>>();
/// <summary>
/// Gets a converter for the given input/output type parameters.
/// If there is no conversion null is returned and the returned delegate does a cast to work
/// with object types.
/// </summary>
public Converter<object, object> CastedTryGet(Type inputType, Type outputType)
{
if (inputType == null)
throw new ArgumentNullException("inputType");
if (outputType == null)
throw new ArgumentNullException("outputType");
var key = new KeyValuePair<Type, Type>(inputType, outputType);
Converter<object, object> result;
if (_castedConverters.TryGetValue(key, out result))
return result;
var untypedTryGetMethod = _castedTryGetGenericMethod.MakeGenericMethod(inputType, outputType);
var objectResult = untypedTryGetMethod.Invoke(this, null);;
if (objectResult == null)
return null;
result = (Converter<object, object>)objectResult;
_castedConverters.Add(key, result);
return result;
}
/// <summary>
/// Gets a converter for the given input/output type parameters.
/// If there is no conversion an exception is thrown and the returned delegate does a cast to work
/// with object types.
/// </summary>
public Converter<object, object> CastedGet(Type inputType, Type outputType)
{
var result = CastedTryGet(inputType, outputType);
if (result == null)
throw new InvalidOperationException("There is no converter registered for the given types.");
return result;
}
/// <summary>
/// Event invoked when a converter for an specific input/output type is not found for
/// the actual thread. Note that global converters will only be used if the Searching
/// event does not return a converter.
/// </summary>
public event SearchingConverterEventHandler Searching;
#endregion
}
}