using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Threading;
using Pfz.Databasing.Managers;
using Pfz.Extensions.ReaderWriterLockExtensions;
using Pfz.Extensions.TypeExtensions;
namespace Pfz.Databasing.Managers
{
/// <summary>
/// Class used to generate the record-assemblies when they are first
/// needed.
/// </summary>
public static class RecordImplementer
{
private static Dictionary<Type, Dictionary<Assembly, Assembly>> fImplementedAssemblies = new Dictionary<Type, Dictionary<Assembly, Assembly>>();
private static ReaderWriterLockSlim fLock = new ReaderWriterLockSlim();
/// <summary>
/// Gets the implemented assembly with all implemented records found
/// in the given assembly.
/// </summary>
public static Assembly GetImplementedRecordsAssemblyFor<TGenerator>(Assembly sourceAssembly)
where
TGenerator: RecordClassGenerator, new()
{
if (sourceAssembly == null)
throw new ArgumentNullException("sourceAssembly");
Assembly result;
Dictionary<Assembly, Assembly> implementedAssemblies;
lock(fImplementedAssemblies)
{
if (!fImplementedAssemblies.TryGetValue(typeof(TGenerator), out implementedAssemblies))
{
implementedAssemblies = new Dictionary<Assembly, Assembly>();
fImplementedAssemblies.Add(typeof(TGenerator), implementedAssemblies);
}
}
using(fLock.ReadLock())
if (implementedAssemblies.TryGetValue(sourceAssembly, out result))
return result;
using(fLock.UpgradeableLock())
{
if (implementedAssemblies.TryGetValue(sourceAssembly, out result))
return result;
TGenerator generator = new TGenerator();
Type[] types = sourceAssembly.GetTypes();
List<Type> usedTypes = new List<Type>();
usedTypes.Add(typeof(IDbCommand));
foreach(Type type in types)
{
if (type.IsInterface && type != typeof(IRecord) && typeof(IRecord).IsAssignableFrom(type))
{
generator.Implement(type);
usedTypes.Add(type);
}
}
if (usedTypes.Count == 0)
{
using(fLock.WriteLock())
implementedAssemblies.Add(sourceAssembly, null);
return null;
}
CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.OutputAssembly = "Pfz.ImplementedRecordsFor_" + sourceAssembly.GetName().Name + "_" + typeof(TGenerator).FullName + ".dll";
options.IncludeDebugInformation = false;
var referencedAssembliesList = options.ReferencedAssemblies;
var referencedAssemblies = usedTypes.GetReferencedAssemblies();
foreach(var assembly in referencedAssemblies)
referencedAssembliesList.Add(assembly.Location);
var compileResult = provider.CompileAssemblyFromSource(options, generator.ToString());
if (compileResult.Errors.HasErrors)
throw new Exception("CodeDom has errors.");
result = compileResult.CompiledAssembly;
using(fLock.WriteLock())
implementedAssemblies.Add(sourceAssembly, result);
}
return result;
}
/// <summary>
/// Gets the implemented type for the given interface record type.
/// </summary>
public static Type GetImplementedTypeFor<TGenerator>(Type recordInterfaceType)
where
TGenerator: RecordClassGenerator, new()
{
if (recordInterfaceType == null)
throw new ArgumentNullException("recordInterfaceType");
if (recordInterfaceType == typeof(IRecord))
throw new ArgumentException("recordInterfaceType can't be the IRecord itself.", "recordInterfaceType");
if (!typeof(IRecord).IsAssignableFrom(recordInterfaceType))
throw new ArgumentException("recordInterfaceType must be a sub-interface of IRecord.", "recordInterfaceType");
Assembly assembly = GetImplementedRecordsAssemblyFor<TGenerator>(recordInterfaceType.Assembly);
return assembly.GetType("Pfz.Databasing.ImplementedRecords." + typeof(TGenerator).FullName + "." + recordInterfaceType.FullName, true);
}
/// <summary>
/// Creates a new record of the given type.
/// Do not call this function if you are not implementing the Database
/// Manager itself.
/// </summary>
public static TRecord Create<TGenerator, TRecord>()
where
TGenerator: RecordClassGenerator, new()
where
TRecord: class, IRecord
{
Type implementedType = GetImplementedTypeFor<TGenerator>(typeof(TRecord));
object result = implementedType.GetConstructor(Type.EmptyTypes).Invoke(null);
return (TRecord)result;
}
/// <summary>
/// Creates the record, using the given database values.
/// </summary>
public static TRecord Create<TGenerator, TRecord>(IDbConnection connection, object[] databaseValues)
where
TGenerator: RecordClassGenerator, new()
where
TRecord: class, IRecord
{
return (TRecord)Create<TGenerator>(typeof(TRecord), connection, databaseValues);
}
private static readonly Type[] fObjectArrayType = new Type[]{typeof(IDbConnection), typeof(object[])};
/// <summary>
/// Creates the record, using the given database values.
/// </summary>
public static object Create<TGenerator>(Type recordType, IDbConnection connection, object[] databaseValues)
where
TGenerator: RecordClassGenerator, new()
{
Type implementedType = GetImplementedTypeFor<TGenerator>(recordType);
object result = implementedType.GetConstructor(fObjectArrayType).Invoke(new object[]{connection, databaseValues});
return result;
}
}
}