| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 | using System;using System.Collections;using System.Collections.Generic;using System.Linq;using System.Linq.Expressions;using System.Reflection;namespace InABox.Core{    public interface ILookupDefinition<TLookup> where TLookup : BaseObject, new()    {        Filter<TLookup>? DefineFilter();        Columns<TLookup> DefineColumns();        Columns<TLookup> RequiredColumns();        SortOrder<TLookup> DefineSortOrder();        string FormatLookup(Dictionary<string, object?> values, IEnumerable<string> exclude);    }    public interface ILookupDefinition<TLookup, TEntity> where TLookup : Entity    {        Filter<TLookup> DefineFilter(TEntity[] items);        /// <summary>        /// Define the columns required for the <c>items</c> parameter of <see cref="DefineFilter(TEntity[])"/>.        /// </summary>        /// <returns></returns>        Columns<TEntity> DefineFilterColumns();    }    public interface IStaticLookupDefinition<TEntity>    {        Dictionary<object, object> DefineLookups(PropertyInfo property);    }    public static class LookupFactory    {        #region LookupCache        private class LookupCacheEntry        {            public LookupCacheEntry(object? factory)            {                DefaultFactory = factory;                CustomFactories = new Dictionary<Type, object>();                StaticFactory = null;            }            public object? DefaultFactory { get; }            public Dictionary<Type, object> CustomFactories { get; }            public object? StaticFactory { get; set; }            public object? GetFactory(Type? type = null)            {                if (type == null)                    return DefaultFactory;                if (CustomFactories.ContainsKey(type))                    return CustomFactories[type];                return DefaultFactory;            }        }        private class LookupCache        {            private static Dictionary<Type, LookupCacheEntry> _cache;            public LookupCache()            {                _cache = new Dictionary<Type, LookupCacheEntry>();                // Load up the default types                var defaulttypes = CoreUtils.TypeList(                    AppDomain.CurrentDomain.GetAssemblies(),                    x => !x.IsAbstract &&                         x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<>))                         )                );                foreach (var type in defaulttypes)                    try                    {                        var intfs = type.GetInterfaces().Where(i =>                            i.IsGenericType                            && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<>))                        );                        foreach (var intf in intfs)                            _cache[intf.GenericTypeArguments.First()] = new LookupCacheEntry(Activator.CreateInstance(type));                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }                // Now load up the specific types                var specifictypes = CoreUtils.TypeList(                    AppDomain.CurrentDomain.GetAssemblies(),                    x =>                        !x.IsAbstract &&                        x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<,>))                        )                );                foreach (var type in specifictypes)                {                    var intfs = type.GetInterfaces().Where(i =>                        i.IsGenericType                        && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<,>))                    );                    foreach (var intf in intfs)                    {                        var lookuptype = intf.GenericTypeArguments.First();                        var entitytype = intf.GenericTypeArguments.Last();                        if (!_cache.ContainsKey(lookuptype))                            _cache[lookuptype] = new LookupCacheEntry(null);                        _cache[lookuptype].CustomFactories[entitytype] = Activator.CreateInstance(type);                    }                }                // Load up the static lookups                var staticlookups = CoreUtils.TypeList(                    AppDomain.CurrentDomain.GetAssemblies(),                    x =>                        !x.IsAbstract &&                        x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(IStaticLookupDefinition<>))                        )                );                foreach (var type in staticlookups)                {                    var intf = type.GetInterfaces().First(i =>                        i.IsGenericType                        && i.GetGenericTypeDefinition().Equals(typeof(IStaticLookupDefinition<>))                    );                    var iType = intf.GenericTypeArguments.First();                    if (!_cache.ContainsKey(iType))                        _cache[iType] = new LookupCacheEntry(null);                    _cache[iType].StaticFactory = Activator.CreateInstance(type);                }            }            public object? GetFactory(Type lookup, Type? entity = null)            {                if (!_cache.ContainsKey(lookup))                    return null;                if (entity == null)                    return _cache[lookup].DefaultFactory;                if (_cache[lookup].CustomFactories.ContainsKey(entity))                    return _cache[lookup].CustomFactories[entity];                return null;            }            public object? GetStaticFactory(Type lookup)            {                if (!_cache.ContainsKey(lookup))                    return null;                return _cache[lookup].StaticFactory;            }        }        private static readonly LookupCache _cache = new LookupCache();        #endregion        #region General Factory Methods        private static object? DoInvoke(Type TLookup, Type TResult, string Method)        {            var factory = _cache.GetFactory(TLookup);            if (factory != null)            {                var filtertype = TResult.MakeGenericType(TLookup);                var method = factory.GetType().GetMethods().FirstOrDefault(m =>                     m.Name.Equals(Method)                     && !m.GetParameters().Any()                     && m.ReturnType == filtertype                );                if (method != null)                    try                    {                        object[] parameters = { };                        return method.Invoke(factory, parameters);                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }            }            return null;        }        // If a specific Entity/Lookup match can't be found,        // this will fall back to the global Lookup filter (if there is one)        // Otherwise, it will return null        private static object? DoInvoke(Type TLookup, Type TEntity, IEnumerable items, Type TResult, string Method)        {            var factory = _cache.GetFactory(TLookup, TEntity);            if (factory != null)            {                var filtertype = TResult.MakeGenericType(TLookup);                var method = factory.GetType().GetMethods().FirstOrDefault(m =>                     m.Name.Equals(Method)                     && m.GetParameters().Any()                     && m.ReturnType == filtertype                     && m.GetParameters().First().ParameterType == items.GetType()                );                if (method != null)                    try                    {                        object[] parameters = { items };                        return method.Invoke(factory, parameters);                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }            }            return DoInvoke(TLookup, TResult, Method);        }        private static object? DoInvoke(Type TLookup, Type TEntity, Type TResult, string Method)        {            var factory = _cache.GetFactory(TLookup, TEntity);            if (factory != null)            {                var method = factory.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(m =>                     m.Name.Split('.').Last().Equals(Method) // For explicit implementations, whose name is ILookupDefinition<TLookup,TEntity>.Method                    && !m.GetParameters().Any()                     && m.ReturnType == TResult                );                if (method != null)                    try                    {                        return method.Invoke(factory, Array.Empty<object>());                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }            }            return null;        }        private static Dictionary<object, object>? DoInvoke(Type TLookup, PropertyInfo property, string Method)        {            var factory = _cache.GetStaticFactory(TLookup);            if (factory != null)            {                var method = factory.GetType().GetMethods().Where(m =>                    m.Name.Equals(Method) &&                    m.GetParameters().SingleOrDefault() != null &&                    m.ReturnType.Equals(typeof(Dictionary<object, object>))                ).FirstOrDefault();                if (method != null)                    try                    {                        object[] parameters = { property };                        return method.Invoke(factory, parameters) as Dictionary<object, object>;                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }            }            return null;        }        private static object? DoInvoke(Type TLookup, Type TReturn, string Method, params object[] args)        {            var factory = _cache.GetFactory(TLookup);            if (factory != null)            {                var method = factory.GetType().GetMethods().Where(m =>                    m.Name.Equals(Method) &&                    m.GetParameters().Length == args.Length &&                    m.ReturnType.Equals(TReturn)                ).FirstOrDefault();                if (method != null)                    try                    {                        return method.Invoke(factory, args);                    }                    catch (Exception e)                    {                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));                    }            }            return null;        }        #endregion        #region Formats        public static string DefaultFormatLookup(Dictionary<string, object?> values, IEnumerable<string> exclude)        {            return string.Join(": ",                values.Where(x => x.Value != null && x.Value.GetType() != typeof(Guid) && (exclude == null || !exclude.Contains(x.Key)))                    .Select(p => p.Value));        }        public static string FormatLookup(Type TLookup, Dictionary<string, object?> values, IEnumerable<string> exclude)        {            var result = DoInvoke(TLookup, typeof(string), "FormatLookup", values, exclude) as string;            if(result is null)            {                return DefaultFormatLookup(values, exclude);            }            return result;        }        public static string FormatLookup<TLookup>(Dictionary<string, object?> values, IEnumerable<string> exclude)        {            return FormatLookup(typeof(TLookup), values, exclude);        }        #endregion        #region Filters        public static IFilter? DefineFilter(Type TLookup)        {            return DoInvoke(TLookup, typeof(Filter<>), "DefineFilter") as IFilter;            //var factory =_cache.GetFactory(TLookup);            //if (factory != null)            //{            //    Type filtertype = typeof(Filter<>).MakeGenericType(TLookup);            //    var method = factory.GetType().GetMethods().Where(m =>            //        m.Name.Equals("DefineFilter") &&            //        m.GetParameters().Any() &&            //        m.GetParameters().First().ParameterType.IsByRef &&            //        m.GetParameters().First().ParameterType.GetElementType().Equals(filtertype)).FirstOrDefault();            //    if (method != null)            //    {            //        try            //        {            //            object[] parameters = new object[] { null };            //            method.Invoke(factory, parameters);            //            return parameters[0];            //        }            //        catch (Exception e)            //        {            //        }            //    }            //}            //return null;        }        public static Filter<TLookup>? DefineFilter<TLookup>()        {            return DefineFilter(typeof(TLookup)) as Filter<TLookup>;        }        public static IFilter? DefineFilter(Type TLookup, Type TEntity, IEnumerable items)        {            return DoInvoke(TLookup, TEntity, items, typeof(Filter<>), "DefineFilter") as IFilter;        }        public static IFilter? DefineFilter<TEntity>(IEnumerable<TEntity> items, Type TLookup)        {            return DoInvoke(TLookup, typeof(TEntity), items, typeof(Filter<>), "DefineFilter") as IFilter;        }        public static Filter<TLookup>? DefineFilter<TEntity, TLookup>(TEntity[] items) where TEntity : Entity where TLookup : Entity        {            return DefineFilter(items, typeof(TLookup)) as Filter<TLookup>;        }                #endregion        #region Columns        public static IColumns DefineColumns(Type TLookup)        {            var result = DoInvoke(TLookup, typeof(Columns<>), "DefineColumns") as IColumns;            if (result == null)            {                result = Columns.Create(TLookup).DefaultColumns();            }            return result;        }        public static Columns<TLookup> DefineColumns<TLookup>()        {            return (DefineColumns(typeof(TLookup)) as Columns<TLookup>)!;        }        public static IColumns DefineFilterColumns(Type TLookup, Type TEntity, IEnumerable items)        {            var result = DoInvoke(TLookup, TEntity, typeof(Columns<>).MakeGenericType(TEntity), "DefineFilterColumns") as IColumns;            result ??= Columns.Create(TLookup).DefaultColumns();            return result;        }        public static IColumns DefineFilterColumns<TEntity>(Type TLookup)        {            var result = DoInvoke(TLookup, typeof(TEntity), typeof(Columns<>).MakeGenericType(typeof(TEntity)), "DefineFilterColumns") as IColumns;            if (result == null)            {                result = Columns.Create(TLookup);            }            return result;        }        public static Columns<TLookup> DefineFilterColumns<TEntity, TLookup>() where TEntity : Entity where TLookup : Entity        {            return DefineFilterColumns<TEntity>(typeof(TLookup)) as Columns<TLookup>;        }        #endregion        #region RequiredColumns        public static IColumns DefaultRequiredColumns(Type TLookup)        {            var result = Columns.Create(TLookup);            var props = DatabaseSchema.Properties(TLookup).Where(x => x.Required);            foreach (var prop in props)                result.Add(prop.Name);            return result;        }        public static Columns<TLookup> DefaultRequiredColumns<TLookup>()            => (DefaultRequiredColumns(typeof(TLookup)) as Columns<TLookup>)!;        public static IColumns RequiredColumns(Type TLookup)        {            var result = DoInvoke(TLookup, typeof(Columns<>), "RequiredColumns") as IColumns;            if (result == null)            {                result = DefaultRequiredColumns(TLookup);            }            return result;        }        public static Columns<TLookup> RequiredColumns<TLookup>()        {            return RequiredColumns(typeof(TLookup)) as Columns<TLookup>;        }                #endregion        #region SortOrder        public static ISortOrder DefineSort(Type TLookup)        {            return DoInvoke(TLookup, typeof(SortOrder<>), "DefineSortOrder") as ISortOrder;        }        public static SortOrder<TLookup> DefineSort<TLookup>()        {            return DefineSort(typeof(TLookup)) as SortOrder<TLookup>;        }                #endregion        #region Static Lookups        public static Dictionary<object, object> DefineLookups(Type type, PropertyInfo property)        {            return DoInvoke(type, property, "DefineLookups");        }        public static Dictionary<object, object> DefineLookups<TLookup>(Expression<Func<TLookup, object>> property)        {            return DefineLookups(typeof(TLookup), CoreUtils.GetPropertyFromExpression(property));        }        #endregion    }    public abstract class BaseObjectLookup<TLookup> : ILookupDefinition<TLookup> where TLookup : BaseObject, new()    {        public virtual string FormatLookup(Dictionary<string, object?> values, IEnumerable<string> exclude)        {            var filtered = new Dictionary<string, object?>();            var cols = DefineColumns();            foreach (var col in cols.Items)                if (values.ContainsKey(col.Property) && values[col.Property] != null && values[col.Property]?.GetType() != typeof(Guid))                    filtered[col.Property] = values[col.Property];            return LookupFactory.DefaultFormatLookup(filtered, exclude);        }        public abstract Columns<TLookup> DefineColumns();        public abstract Columns<TLookup> RequiredColumns();        public abstract Filter<TLookup>? DefineFilter();        public abstract SortOrder<TLookup> DefineSortOrder();    }    public abstract class EntityLookup<TLookup> : BaseObjectLookup<TLookup> where TLookup : Entity, new()    {        public override Columns<TLookup> DefineColumns()        {            return new Columns<TLookup>(x => x.ID);        }        public override Columns<TLookup> RequiredColumns()        {            return LookupFactory.DefaultRequiredColumns<TLookup>();        }    }}
 |