using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Pfz.Databasing.Filtering;
using Pfz.Databasing.Managers;
using Pfz.Extensions.AttributeExtensions;
using PathPair = System.Collections.Generic.KeyValuePair<System.Collections.ObjectModel.ReadOnlyCollection<Pfz.Databasing.Managers.PropertyInfoPath>, System.Type[][]>;
namespace Pfz.Databasing.Tiers
{
/// <summary>
/// Tier that allows for you to execute queries over a base inheritance level
/// and load all the "persisted childs" for such inheritance.
/// For example:
/// IIdRecord -> base interface for any object that uses only Id as it's key.
/// INamedRecord -> base interface for any object that has a Name, and is child
/// of IIdRecord.
/// IProduct -> persisted interface of products.
/// IPerson -> persisted interface of persons.
///
/// Products and Persons are named records. So, executing a query over
/// a INamedRecord must load all matching records from Products and Persons.
/// </summary>
public sealed class InheritanceTier:
TierBase
{
private static readonly MethodInfo fFastLoadByPartialSql = typeof(IDatabaseManager).GetMethod("FastLoadByPartialSql");
private static readonly MethodInfo fTryLoadByPrimaryKey = typeof(IDatabaseManager).GetMethod("TryLoadByPrimaryKey", new Type[]{typeof(IDatabaseConnection), typeof(object[])});
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override T TryLoadByPrimaryKey<T>(IDatabaseConnection connection, params object[] primaryKeyValues)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
object[] parameters = new object[]{connection, primaryKeyValues};
T result = null;
foreach(var type in persistedTypes)
{
MethodInfo method = fTryLoadByPrimaryKey.MakeGenericMethod(type);
object possibleResult = method.Invoke(BaseManager, parameters);
if (possibleResult != null)
{
if (result != null)
throw new DatabaseException("More than one record found by the primary key.");
result = (T)possibleResult;
}
}
return result;
}
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override IFastEnumerator<T> FastLoadByPartialSql<T>(IDatabaseConnection connection, string sql, params object[] parameterValues)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
return new p_LoadByPartialSqlFastEnumerator<T>(BaseManager, persistedTypes, connection, sql, parameterValues);
}
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override IFastEnumerator<object[]> AdvancedLoad(IDatabaseConnection connection, AdvancedLoadParameters parameters)
{
var persistedTypes = p_GetPersistedTypes(parameters.InitialRecordType);
List<FilterGroup> filterGroups = null;
if (parameters.Filter != null)
filterGroups = p_GetFilterGroups(parameters.Filter);
var persistedSelectTypePaths = p_GetPersistedSelectTypePaths(parameters.SelectPaths);
return new p_AdvancedLoad(BaseManager, persistedTypes, filterGroups, connection, persistedSelectTypePaths, parameters.Clone());
}
private static readonly MethodInfo fCountRecordsByFilterGroup = typeof(IDatabaseManager).GetMethod("CountRecordsByFilterGroup");
/// <summary>
/// Returns the sum of the count of all persisted records that
/// implement the given interface and also matches the filter.
/// </summary>
public override int CountRecordsByFilterGroup<T>(IDatabaseConnection connection, FilterGroup filterGroup)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
int count = 0;
object[] parameters = new object[]{connection, null};
if (filterGroup == null)
{
foreach(var persistedType in persistedTypes)
{
var method = fCountRecordsByFilterGroup.MakeGenericMethod(persistedType);
int value = (int)method.Invoke(BaseManager, parameters);
count += value;
}
}
else
{
List<FilterGroup> filterGroups = p_GetFilterGroups(filterGroup);
foreach(var persistedType in persistedTypes)
{
var method = fCountRecordsByFilterGroup.MakeGenericMethod(persistedType);
foreach(var group in filterGroups)
{
parameters[1] = group;
int value = (int)method.Invoke(BaseManager, parameters);
count += value;
}
}
}
return count;
}
private List<PathPair> p_GetPersistedSelectTypePaths(ReadOnlyCollection<PropertyInfoPath> selectPaths)
{
var changeablePaths = new HashSet<PropertyInfoPath>();
foreach(var selectPath in selectPaths)
{
var path = selectPath;
while(path.Length > 0)
{
var property = path[path.Length-1];
var propertyType = property.PropertyType;
if (typeof(IRecord).IsAssignableFrom(propertyType))
{
var persistedTypes = p_GetPersistedTypes(propertyType);
if (persistedTypes.Length != 1 || persistedTypes[0] != propertyType)
changeablePaths.Add(path);
}
path = path.SubPath(0, path.Length-1);
}
}
var result = new List<PathPair>();
result.Add(new PathPair(selectPaths, null));
foreach(var changeablePath in changeablePaths)
{
// must be read here as it changes at each iteraction.
int count = result.Count;
for(int i=0; i<count; i++)
{
PathPair pair = result[i];
p_CreatePersistedPaths(result, changeablePath, pair);
}
}
int lastCount = result.Count;
for(int i=lastCount-1; i>=0; i--)
{
var pathPair = result[i];
if (p_ContainsAbstractPaths(pathPair))
result.RemoveAt(i);
}
return result;
}
private static bool p_ContainsAbstractPaths(PathPair pair)
{
var selectPaths = pair.Key;
var typePaths = pair.Value;
int count = selectPaths.Count;
for(int i=0; i<count; i++)
{
var selectPath = selectPaths[i];
Type[] typePath = null;
if (typePaths != null)
typePath = typePaths[i];
if (p_IsAbstractPath(selectPath, typePath))
return true;
}
return false;
}
private static bool p_IsAbstractPath(PropertyInfoPath selectPath, Type[] typePath)
{
int count = selectPath.Length;
for (int i=0; i<count; i++)
{
var propertyInfo = selectPath[i];
var propertyType = propertyInfo.PropertyType;
if (!typeof(IRecord).IsAssignableFrom(propertyType))
continue;
if (propertyType.ContainsCustomAttribute<DatabasePersistedAttribute>())
continue;
if (typePath == null)
return true;
Type persistedType = typePath[i];
if (persistedType == null)
return true;
}
return false;
}
private void p_CreatePersistedPaths(List<PathPair> list, PropertyInfoPath changeablePath, PathPair sourcePair)
{
var propertyInfo = changeablePath[changeablePath.Length-1];
var propertyType = propertyInfo.PropertyType;
var persistedTypes = p_GetPersistedTypes(propertyType);
foreach(var persistedType in persistedTypes)
{
var sourcePaths = sourcePair.Key;
var sourceTypePaths = sourcePair.Value;
int count = sourcePaths.Count;
var typePaths = new Type[count][];
for(int i=0; i<count; i++)
{
var sourcePath = sourcePaths[i];
if (sourcePath.Length < changeablePath.Length)
continue;
var comparePath = sourcePath;
if (comparePath.Length > changeablePath.Length)
comparePath = comparePath.SubPath(0, changeablePath.Length);
if (!comparePath.Equals(changeablePath))
continue;
Type[] typePath = null;
if (sourceTypePaths != null)
typePath = sourceTypePaths[i];
if (typePath != null)
typePath = (Type[])typePath.Clone();
else
typePath = new Type[sourcePath.Length];
typePaths[i] = typePath;
typePath[changeablePath.Length-1] = persistedType;
}
list.Add(new PathPair(sourcePaths, typePaths));
}
}
/// <summary>
/// Adds inheritance support to this method.
/// </summary>
public override IFastEnumerator<T> FastLoadByFilterGroup<T>(IDatabaseConnection connection, FilterGroup filterGroup)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
List<FilterGroup> filterGroups = null;
if (filterGroup != null)
filterGroups = p_GetFilterGroups(filterGroup);
return new p_FastLoadByFilterGroup<T>(BaseManager, persistedTypes, filterGroups, connection);
}
private List<FilterGroup> p_GetFilterGroups(FilterGroup baseGroup)
{
var allChangeablePaths = new HashSet<PropertyInfoPath>();
p_AddChangeablePathsRecursive(allChangeablePaths, baseGroup);
var result = new List<FilterGroup>();
result.Add(baseGroup);
if (allChangeablePaths.Count == 0)
return result;
foreach(var path in allChangeablePaths)
{
int count = result.Count;
for(int i=0; i<count; i++)
{
FilterGroup actualGroup = result[i];
p_CreatePersistedGroups(result, path, actualGroup);
}
}
int lastCount = result.Count;
for (int i=lastCount-1; i>=0; i--)
{
FilterGroup group = result[i];
if (p_ContainsAbstractPaths(group))
result.RemoveAt(i);
}
return result;
}
private bool p_ContainsAbstractPaths(FilterGroup group)
{
foreach(var subGroup in group.fSubGroups)
if (p_ContainsAbstractPaths(subGroup))
return true;
foreach(var filter in group.fFilters)
{
int itemIndex = -1;
foreach(var property in filter.Path)
{
itemIndex++;
var propertyType = property.PropertyType;
if (!typeof(IRecord).IsAssignableFrom(propertyType))
continue;
if (propertyType.ContainsCustomAttribute<DatabasePersistedAttribute>())
continue;
if (filter.fTypePath == null)
return true;
if (filter.fTypePath[itemIndex] == null)
return true;
}
}
return false;
}
private void p_CreatePersistedGroups(List<FilterGroup> groups, PropertyInfoPath pathToReplace, FilterGroup baseGroup)
{
PropertyInfo propertyInfo = pathToReplace[pathToReplace.Length-1];
foreach(var persistedType in p_GetPersistedTypes(propertyInfo.PropertyType))
{
var newGroup = baseGroup.Clone();
p_ReplaceRecursive(newGroup, pathToReplace, persistedType);
groups.Add(newGroup);
}
}
private void p_ReplaceRecursive(FilterGroup group, PropertyInfoPath pathToReplace, Type persistedType)
{
foreach(var subGroup in group.fSubGroups)
p_ReplaceRecursive(subGroup, pathToReplace, persistedType);
foreach(var filter in group.fFilters)
{
var path = filter.Path;
if (path.Length < pathToReplace.Length)
continue;
var subPath = path;
if (path.Length > pathToReplace.Length)
subPath = path.SubPath(0, pathToReplace.Length);
if (!subPath.Equals(pathToReplace))
continue;
if (filter.fTypePath == null)
filter.fTypePath = new Type[path.Length];
filter.fTypePath[subPath.Length-1] = persistedType;
}
}
private void p_AddChangeablePathsRecursive(HashSet<PropertyInfoPath> changeablePaths, FilterGroup group)
{
foreach(var subGroup in group.fSubGroups)
p_AddChangeablePathsRecursive(changeablePaths, subGroup);
foreach(var filter in group.fFilters)
{
var path = filter.Path;
while(path.Length > 0)
{
var propertyInfo = path[path.Length-1];
var propertyType = propertyInfo.PropertyType;
if (typeof(IRecord).IsAssignableFrom(propertyType))
{
var persistedTypes = p_GetPersistedTypes(propertyType);
if (persistedTypes.Length != 1 || persistedTypes[0] != propertyType)
changeablePaths.Add(path);
}
path = path.SubPath(0, path.Length-1);
}
}
}
private SearchMode fSearchMode;
/// <summary>
/// Gets or sets the search type for inheritors.
/// The default is SameAssembly.
/// </summary>
public SearchMode SearchMode
{
get
{
return fSearchMode;
}
set
{
if (value == fSearchMode)
return;
if (value == SearchMode.GivenAssemblies)
{
fGivenAssemblies = new HashSet<Assembly>();
AppDomain.CurrentDomain.AssemblyLoad += p_AssemblyLoad;
}
else
fGivenAssemblies = null;
if (value == SearchMode.AllLoadedAssemblies)
AppDomain.CurrentDomain.AssemblyLoad += p_AssemblyLoad;
else
AppDomain.CurrentDomain.AssemblyLoad -= p_AssemblyLoad;
fSearchMode = value;
}
}
private void p_AssemblyLoad(object sender, EventArgs e)
{
lock(fDictionaryOfPersistedTypesLock)
fDictionaryOfPersistedTypes = null;
}
private HashSet<Assembly> fGivenAssemblies;
/// <summary>
/// Adds an assembly to search for inheritors, and sets the SearchType
/// to GivenAssemblies.
/// </summary>
public void AddSearchAssembly(Assembly assembly)
{
SearchMode = SearchMode.GivenAssemblies;
fGivenAssemblies.Add(assembly);
lock(fDictionaryOfPersistedTypesLock)
fDictionaryOfPersistedTypes = null;
}
private object fDictionaryOfPersistedTypesLock = new object();
private volatile Dictionary<Type, Type[]> fDictionaryOfPersistedTypes;
/// <summary>
/// Gets a read-only array of all persisted types based on the given base-type.
/// </summary>
public ReadOnlyCollection<Type> GetPersistedTypes(Type baseType)
{
if (baseType == null)
throw new ArgumentNullException("baseType");
Type[] result = p_GetPersistedTypes(baseType);
return new ReadOnlyCollection<Type>(result);
}
private Type[] p_GetPersistedTypes(Type baseType)
{
Type[] result;
lock (fDictionaryOfPersistedTypesLock)
{
var dictionary = fDictionaryOfPersistedTypes;
if (dictionary != null)
if (dictionary.TryGetValue(baseType, out result))
return result;
}
if (!baseType.IsInterface)
throw new ArgumentException("baseType must be an interface type.", "baseType");
if (!typeof(IRecord).IsAssignableFrom(baseType))
throw new ArgumentException("baseType must be a IRecord.", "baseType");
List<Type> list = new List<Type>();
switch (fSearchMode)
{
case SearchMode.SameAssembly:
foreach (var type in baseType.Assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
case SearchMode.AllLoadedAssemblies:
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
foreach (var type in assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
case SearchMode.GivenAssemblies:
foreach (var assembly in fGivenAssemblies)
foreach (var type in assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
}
result = list.ToArray();
lock (fDictionaryOfPersistedTypesLock)
{
var dictionary = fDictionaryOfPersistedTypes;
if (dictionary == null)
{
dictionary = new Dictionary<Type, Type[]>();
fDictionaryOfPersistedTypes = dictionary;
}
dictionary[baseType] = result;
}
return result;
}
private static void p_AddAssembly(List<Type> list, Type baseType, Type type)
{
if (!type.IsInterface)
return;
if (!baseType.IsAssignableFrom(type))
return;
if (type.ContainsCustomAttribute<DatabasePersistedAttribute>())
list.Add(type);
}
private sealed class p_LoadByPartialSqlFastEnumerator<T>:
IFastEnumerator<T>
where
T: class, IRecord
{
private IDatabaseManager fManager;
private IEnumerator fPersistedTypesEnumerator;
private IDatabaseConnection fConnectionAndTransaction;
private string fSql;
private object[] fParameterValues;
IFastEnumerator fFastEnumerator;
private object[] fMethodParameters;
internal p_LoadByPartialSqlFastEnumerator(IDatabaseManager manager, Type[] persistedTypes, IDatabaseConnection connection, string sql, object[] parameterValues)
{
fManager = manager;
fConnectionAndTransaction = connection;
fSql = sql;
fParameterValues = parameterValues;
fPersistedTypesEnumerator = persistedTypes.GetEnumerator();
fMethodParameters = new object[]{connection, sql, parameterValues};
if (fPersistedTypesEnumerator.MoveNext())
{
Type type = (Type)fPersistedTypesEnumerator.Current;
object fastEnumerator = fFastLoadByPartialSql.MakeGenericMethod(type).Invoke(fManager, fMethodParameters);
fFastEnumerator = (IFastEnumerator)fastEnumerator;
}
}
public void Dispose()
{
var fastEnumerator = fFastEnumerator;
if (fFastEnumerator != null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
}
fPersistedTypesEnumerator = null;
}
public T GetNext()
{
if (fFastEnumerator == null)
return null;
var fastEnumerator = fFastEnumerator;
object result = fastEnumerator.GetNext();
while(result == null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
if (!fPersistedTypesEnumerator.MoveNext())
return null;
Type type = (Type)fPersistedTypesEnumerator.Current;
fastEnumerator = (IFastEnumerator)fFastLoadByPartialSql.MakeGenericMethod(type).Invoke(fManager, fMethodParameters);
fFastEnumerator = fastEnumerator;
result = fFastEnumerator.GetNext();
}
return (T)result;
}
object IFastEnumerator.GetNext()
{
return GetNext();
}
}
private sealed class p_AdvancedLoad:
IFastEnumerator<object[]>
{
private static readonly MethodInfo fAdvancedLoad = typeof(IDatabaseManager).GetMethod("AdvancedLoad");
private Type[] fPersistedTypes;
private int fPersistedTypesIndex;
private IDatabaseManager fBaseManager;
private IDatabaseConnection fConnection;
private List<FilterGroup> fFilterGroups;
private int fFilterGroupIndex;
private List<PathPair> fSelectPathPairs;
private int fSelectPathPairIndex;
private AdvancedLoadParameters fAdvancedLoadParameters;
private IFastEnumerator<object[]> fFastEnumerator;
private object[] fParameters;
internal p_AdvancedLoad(IDatabaseManager baseManager, Type[] persistedTypes, List<FilterGroup> filterGroups, IDatabaseConnection connection, List<PathPair> selectPathPairs, AdvancedLoadParameters parameters)
{
fBaseManager = baseManager;
fPersistedTypes = persistedTypes;
fFilterGroups = filterGroups;
fConnection = connection;
fSelectPathPairs = selectPathPairs;
fAdvancedLoadParameters = parameters;
if (persistedTypes.Length == 0)
return;
FilterGroup filterGroup = null;
if (filterGroups != null)
{
if (filterGroups.Count == 0)
return;
filterGroup = filterGroups[0];
}
if (selectPathPairs.Count == 0)
return;
fAdvancedLoadParameters.Filter = filterGroup;
fParameters = new object[]{connection, fAdvancedLoadParameters};
fPersistedTypesIndex = fPersistedTypes.Length;
fSelectPathPairIndex = -1;
p_GetNextValidEnumerator();
}
public void Dispose()
{
var fastEnumerator = fFastEnumerator;
if (fastEnumerator != null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
}
}
public object[] GetNext()
{
while(true)
{
if (fFastEnumerator == null)
return null;
object[] result = fFastEnumerator.GetNext();
if (result != null)
return result;
fFastEnumerator.Dispose();
fFastEnumerator = null;
p_GetNextValidEnumerator();
}
}
private void p_GetNextValidEnumerator()
{
while(true)
{
fPersistedTypesIndex++;
if (fPersistedTypesIndex >= fPersistedTypes.Length)
{
fSelectPathPairIndex++;
if (fSelectPathPairIndex >= fSelectPathPairs.Count)
{
if (fFilterGroups == null)
return;
fFilterGroupIndex++;
if (fFilterGroupIndex >= fFilterGroups.Count)
return;
FilterGroup filterGroup = fFilterGroups[fFilterGroupIndex];
fAdvancedLoadParameters.Filter = filterGroup;
fSelectPathPairIndex = 0;
}
var pair = fSelectPathPairs[fSelectPathPairIndex];
fAdvancedLoadParameters.SelectPaths = pair.Key;
fAdvancedLoadParameters.TypePaths = pair.Value;
if (fFilterGroups != null)
{
FilterGroup filterGroup = fFilterGroups[fFilterGroupIndex];
if (!p_PathsMatch(pair, filterGroup))
continue;
}
fPersistedTypesIndex = 0;
}
Type type = fPersistedTypes[fPersistedTypesIndex];
fAdvancedLoadParameters.InitialRecordType = type;
fFastEnumerator = (IFastEnumerator<object[]>)fAdvancedLoad.Invoke(fBaseManager, fParameters);
return;
}
}
private bool p_PathsMatch(PathPair pair, FilterGroup filterGroup)
{
Dictionary<PropertyInfoPath, Type[]> dictionary = new Dictionary<PropertyInfoPath, Type[]>();
var paths = pair.Key;
var typePaths = pair.Value;
if (typePaths != null)
{
int count = paths.Count;
for(int i=0; i<count; i++)
{
var path = paths[i];
var typePath = typePaths[i];
if (typePath != null)
dictionary[path] = typePath;
}
}
return p_CheckMatchRecursive(dictionary, filterGroup);
}
private bool p_CheckMatchRecursive(Dictionary<PropertyInfoPath, Type[]> dictionary, FilterGroup filterGroup)
{
foreach(var subGroup in filterGroup.fSubGroups)
if (!p_CheckMatchRecursive(dictionary, subGroup))
return false;
foreach(var filter in filterGroup.fFilters)
{
Type[] typePath;
if (!dictionary.TryGetValue(filter.Path, out typePath))
if (filter.fTypePath != null)
return false;
if (!object.Equals(typePath, filter.fTypePath))
return false;
}
return true;
}
object IFastEnumerator.GetNext()
{
return GetNext();
}
}
private sealed class p_FastLoadByFilterGroup<T>:
IFastEnumerator<T>
where
T: class, IRecord
{
private static MethodInfo fFastLoadByFilterGroup = typeof(IDatabaseManager).GetMethod("FastLoadByFilterGroup");
private IDatabaseManager fBaseManager;
private Type[] fPersistedTypes;
private List<FilterGroup> fFilterGroups;
private int fPersistedTypesIndex;
private int fFilterGroupIndex;
private IFastEnumerator<T> fFastEnumerator;
private object[] fParameters;
internal p_FastLoadByFilterGroup(IDatabaseManager baseManager, Type[] persistedTypes, List<FilterGroup> filterGroups, IDatabaseConnection connection)
{
fBaseManager = baseManager;
fPersistedTypes = persistedTypes;
fFilterGroups = filterGroups;
if (fPersistedTypes.Length == 0)
return;
Type persistedType = persistedTypes[0];
FilterGroup group = null;
if (fFilterGroups != null && fFilterGroups.Count > 0)
group = filterGroups[0];
fParameters = new object[]{connection, group};
var method = fFastLoadByFilterGroup.MakeGenericMethod(persistedType);
fFastEnumerator = (IFastEnumerator<T>)method.Invoke(fBaseManager, fParameters);
}
public void Dispose()
{
var fastEnumerator = fFastEnumerator;
if (fastEnumerator != null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
}
}
public T GetNext()
{
if (fFastEnumerator == null)
return null;
while(true)
{
T result = fFastEnumerator.GetNext();
if (result != null)
return result;
fFastEnumerator.Dispose();
fFastEnumerator = null;
fPersistedTypesIndex++;
if (fPersistedTypesIndex >= fPersistedTypes.Length)
{
fPersistedTypesIndex = 0;
fFilterGroupIndex++;
if (fFilterGroups == null || fFilterGroupIndex >= fFilterGroups.Count)
return null;
FilterGroup group = fFilterGroups[fFilterGroupIndex];
fParameters[1] = group;
}
Type persistedType = fPersistedTypes[fPersistedTypesIndex];
var method = fFastLoadByFilterGroup.MakeGenericMethod(persistedType);
fFastEnumerator = (IFastEnumerator<T>)method.Invoke(fBaseManager, fParameters);
}
}
object IFastEnumerator.GetNext()
{
return GetNext();
}
}
}
}