Monday, November 20, 2006

DynamicClassFactory - Implementation

(Continuing my previous post)
Following is the actual implementation:


using System;
using System.Collections;
using System.Collections.Specialized;
using System.CodeDom.Compiler;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.CSharp;

namespace DynamicClassFactory
{
/// <summary>
/// A dynamic ClassFactory.
/// This class has one static method that is used to create a new instance of an object.
/// When the class is accessed for the first time (i.e. the static ctor is called),
/// it loads the definition of the ClassFactory (including the set of supported classes) from
/// the config file. This ClassFactory is compiled and loaded to memory. Subsequently, every
/// call to the CreateInstance method will effectively return a new object of the requested class,
/// with minimum performance overhead (because the instance is created by simply calling the "new"
/// operator and not by using the Activator).
/// It is internally implemented as a singleton in order to ensure that the ClassFactory is
/// created only once for each AppDomain.
/// In order to provide maximum efficiency, the instantiations are done by giving the index
/// of the item type requested. However, in order to provide simplicity as well, it is possible
/// to generate the instance of a type given its name (instead of index), with a small performance-penalty.
///
/// The ClassFactory has the possibility to add item types at run-time. Each such addition requires a
/// runtime re-compilation of the internal implementation. Therefore, when many types must be added at once,
/// it is better to use the Add(ICollection) method, which performs the compilation only once for the whole list.
/// </summary>
public class ClassFactory
{
// Reference to the actual instance
private static IClassFactory instance = null;
// List of items that can be instantiated via the factory.
// This list is kept in order to be able to recreate the factory on-the-fly
private static ArrayList items;
// Last automatically generated index of item
private static int lastAutoKey;
// Lookup-table to retrieve the index of an item given its name
private static HybridDictionary stringLookupTable;
// Hide the ctor to enforce the singleton
private ClassFactory() {}
// Static ctor - called only once for each AppDomain
// This method loads information from the config file and then calls the init() method
// The config file must include a DynamicClassFactoryItemList section with all types
// that could be instantiated by the ClassFactory.
static ClassFactory()
{
try
{
lastAutoKey = 1000000;
stringLookupTable =
new HybridDictionary();
// Load items from configuration
DynamicClassFactoryItem[] factoryItems = (DynamicClassFactoryItem[])ConfigurationManager.GetSection("DynamicClassFactoryItemList");
if (factoryItems == null)
{
throw new Exception("DynamicClassFactory could not read the configuration file.");
}

items =
new ArrayList(factoryItems);
foreach (DynamicClassFactoryItem item in items)
{
generateKey(item);
stringLookupTable[item.ClassName] = item.Key;
}

init();
}
catch (Exception ex)
{
logException(
"The DynamicClassFactory static ctor generated an exception: ", ex);
throw;
}
}

/// <summary>
/// Initializes the ClassFactory.
/// Creates the code for the ClassFactory and compiles it.
/// If for any reason the ClassFactory could not be properly created, an exception is fired.
/// </summary>
private static void init()
{
// See next post....
}

/// <summary>
/// Returns a new instance of the class whose key in the config file corresponds to the
/// id parameter.
/// </summary>
/// <param name="id">The key of the class to be created</param>
/// <returns>A new instance of the class, or null if there is no corresponding key in the ClassFactory</returns>
public static object CreateInstance(int id, params object[] args)
{
try
{
return instance.CreateInstance(id, args);
}
catch (Exception e)
{
logException(
"An Exception occurred in CreateInstance("+id.ToString() +"). ", e);
throw;
}
}

/// <summary>
/// Returns a new instance of the given type with the given arguments.
/// If the typeName is unknown - an exception is thrown.
/// </summary>
/// <param name="typeName">Name of the type to instantiate</param>
/// <param name="args">Arguments for the constructor</param>
/// <returns>A new instance of the class</returns>
public static object CreateInstance(string typeName, params object[] args)
{
try
{
int key = (int)stringLookupTable[typeName];
return instance.CreateInstance(key, args);
}
catch (Exception e)
{
logException(
"An Exception occurred in CreateInstance(" + typeName + "). ", e);
throw;
}
}

/// <summary>
/// Adds the given item to the list of item types generated by the Factory.
/// This method causes the internal implementation to be re-compiled before it returns.
/// If several types have to be added to the factory, it is recommended to use the Add(ICollection)
/// overload of this method.
/// </summary>
public static void Add(DynamicClassFactoryItem item)
{
try
{
lock (items.SyncRoot)
{
if (addItem(item))
{
init();
}
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding item with key " + item.Key.ToString() + ". ", e);
}
}

/// <summary>
/// Adds the given list of items to the list of item types generated by the factory.
/// This method causes the internal implementation to be re-compiled only once for the whole
/// list before it returns.
/// </summary>
public static void Add(ICollection newItems)
{
try
{
bool itemAdded = false;

lock (items.SyncRoot)
{
foreach (DynamicClassFactoryItem item in newItems)
{
if (addItem(item))
{
itemAdded =
true;
}
}

if (itemAdded)
{
init();
}
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding collection. ", e);
}
}

// Add the given item to the list of supported items. If the item's Key is negative, the item
// is automatically being assigned a new, unique Key, in the range > 1,000,000
private static bool addItem(DynamicClassFactoryItem item)
{
try
{
lock (items.SyncRoot)
{
generateKey(item);

if ((!items.Contains(item)) && (!stringLookupTable.Contains(item.ClassName)))
{
items.Add(item);
stringLookupTable[item.ClassName] = item.Key;
return true;
}
return false;
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding item with key " + item.Key + ". ", e);
}
return false;
}

// Checks whether the given item has a legal Key (>= 0). If it doesn't,
// a new, unique Key is generated in the range > 1,000,000
private static void generateKey(DynamicClassFactoryItem item)
{
if (item.Key < 0)
{
item.Key =
Interlocked.Increment(ref lastAutoKey);
}
}

private static void logException(string message, Exception e)
{
StringBuilder sb = new StringBuilder(1000);
sb.AppendFormat(
"{0}. Exception Message: {1}, Stack Trace: {2}.{3}", message, e.Message, e.StackTrace, Environment.NewLine);

Exception current = e.InnerException;
while (current != null)
{
sb.AppendFormat(
"Inner Exception Message: {0}.{1}", current.Message, Environment.NewLine);
current = current.InnerException;
}

string toLog = sb.ToString();
Debug.Write(toLog);
EventLogWrapper.WriteEntry(EventType.Error, toLog);
}
}

// Wrapper to the Windows Event Log.
internal class EventLogWrapper
{
private static readonly EventLogWrapper instance = new EventLogWrapper();
private EventLog eLog;
private EventLogWrapper()
{
if (!EventLog.SourceExists("DCF"))
{
EventLog.CreateEventSource("DCF", "");
}

eLog =
new EventLog();
eLog.Log =
"";
eLog.Source =
"DCF";
}
public static void WriteEntry(EventType eType, string message, params object[] args)
{
if (args.Length > 0)
{
message =
string.Format(message, args);
}

instance.eLog.WriteEntry(message, (
EventLogEntryType)eType);
}
}

internal enum EventType
{
Error =
EventLogEntryType.Error,
Warning =
EventLogEntryType.Warning,
Information =
EventLogEntryType.Information
}
}

No comments: