Kenric Nugteren 1 месяц назад
Родитель
Сommit
9e25d0f5dd
1 измененных файлов с 346 добавлено и 451 удалено
  1. 346 451
      InABox.Core/CoreUtils.cs

+ 346 - 451
InABox.Core/CoreUtils.cs

@@ -53,27 +53,10 @@ namespace InABox.Core
     [LibraryInitializer]
     public static class CoreUtils
     {
-
-
-        
-        private static readonly Dictionary<string, Type> entities = new Dictionary<string, Type>();
-
-        public static Dictionary<string, long> TypeListSummary = new Dictionary<string, long>();
-
-        private static readonly ConcurrentDictionary<Type, IColumns> _columnscache = new ConcurrentDictionary<Type, IColumns>();
-
         private static readonly Regex StripHtmlStyles = new Regex(@"<style(.|\n)*?</style>", RegexOptions.Compiled);
         private static readonly Regex StripHtmlTags = new Regex(@"<(.|\n)*?>", RegexOptions.Compiled);
         private static readonly Regex StripSpecialCharacters = new Regex(@"&#(.|\n)*?;|&nbsp;|&gt;|&lt;&quot;", RegexOptions.Compiled);
         private static readonly Regex StripLineBreaks = new Regex(@"( |\r?\n|\r|\n)\1+", RegexOptions.Compiled);
-        
-        /// <summary>
-        /// Return all <see cref="Type"/> that have been loaded through use of the <see cref="RegisterClasses(Assembly[])"/> function.
-        /// </summary>
-        /// <remarks>
-        /// This includes every non-abstract type.
-        /// </remarks>
-        public static IEnumerable<Type> Entities => entities.Values;
 
         public static long GenerateSequence()
         {
@@ -262,7 +245,6 @@ namespace InABox.Core
             return enumType.GetField(name).GetCustomAttributes(false).OfType<T>().FirstOrDefault();
         }
 
-
         public class GenericTypeBuilder
         {
             public Type Type { get; private set; }
@@ -350,10 +332,44 @@ namespace InABox.Core
             return new GenericTypeBuilder(type);
         }
 
+        public static bool ContainsInheritedGenericType(this Type type, Type generic)
+        {
+            if (type == null)
+                return false;
+            if (type.GenericTypeArguments.Contains(generic))
+                return true;
+            if (type.BaseType == null)
+                return false;
+            return type.BaseType.ContainsInheritedGenericType(generic);
+        }
+
+        public static IEnumerable<Type> GetInheritedGenericTypeArguments(this Type type)
+        {
+            if (type == null)
+                return new Type[] { };
+            if (type.GetGenericArguments().Any())
+                return type.GenericTypeArguments;
+            if (type.BaseType == null)
+                return new Type[] { };
+            return type.BaseType.GetInheritedGenericTypeArguments();
+        }
+
         #endregion
 
         #region Entities + Types
 
+        private static readonly Dictionary<string, Type> entities = new Dictionary<string, Type>();
+
+        public static Dictionary<string, long> TypeListSummary = new Dictionary<string, long>();
+        
+        /// <summary>
+        /// Return all <see cref="Type"/> that have been loaded through use of the <see cref="RegisterClasses(Assembly[])"/> function.
+        /// </summary>
+        /// <remarks>
+        /// This includes every non-abstract type.
+        /// </remarks>
+        public static IEnumerable<Type> Entities => entities.Values;
+
         public static void RegisterClasses()
         {
             RegisterClasses(typeof(CoreUtils).Assembly);
@@ -547,181 +563,13 @@ namespace InABox.Core
 
         #endregion
 
-        public static object? ChangeType(object? value, Type type)
-        {
-            if ((value == null || value is DBNull) && type.GetTypeInfo().IsGenericType)
-                return Activator.CreateInstance(type);
-
-            if (value == null)
-                return null;
-
-            if (value is DBNull)
-                return null;
-
-            if (type == value.GetType())
-                return value;
-
-            if (type == typeof(bool))
-                return value.Equals(bool.TrueString) || value.Equals(1) || value.Equals("1");
-
-            if (type.GetTypeInfo().IsEnum)
-            {
-                if (value.GetType().IsArray)
-                    return (value as Array).Cast<object>().Select(x => Enum.ToObject(type, x)).ToArray();
-                if (value is string)
-                    return Enum.Parse(type, value as string);
-                if (value is IEnumerable<object> list)
-                    return list.Select(x => Enum.ToObject(type, x)).ToArray();
-                return Enum.ToObject(type, value);
-            }
-
-            if (value is string && type == typeof(Guid))
-                try
-                {
-                    return new Guid(value as string);
-                }
-                catch
-                {
-                    return Guid.Empty;
-                }
-
-            if (value is string && type == typeof(byte[]))
-                return Convert.FromBase64String(value as string);
-
-            if (value is IEnumerable objList)
-            {
-                if(type == typeof(Guid))
-                {
-                    return objList.Cast<object?>().Select(x => ChangeType<Guid>(x)).ToArray();
-                }
-                else if (type.IsArray)
-                {
-                    var elementType = type.GetElementType();
-                    var collection = value as ICollection<object?> ?? objList.Cast<object?>().ToList();
-
-                    var arr = Array.CreateInstance(elementType, collection.Count);
-                    var i = 0;
-                    foreach(var val in collection)
-                    {
-                        arr.SetValue(ChangeType(val, elementType), i);
-                        ++i;
-                    }
-                    return arr;
-                }
-                else if (type.IsGenericType)
-                {
-                    var typeDef = type.GetGenericTypeDefinition();
-                    if(typeDef == typeof(Dictionary<,>))
-                    {
-                        var dict = (Activator.CreateInstance(type) as IDictionary)!;
-                        var dictDef = type.GetSuperclassDefinition(typeof(Dictionary<,>))!;
-                        var keyType = dictDef.GenericTypeArguments[0];
-                        var valType = dictDef.GenericTypeArguments[1];
-                        if(value is IDictionary fromDict)
-                        {
-                            foreach(DictionaryEntry entry in fromDict)
-                            {
-                                dict[ChangeType(entry.Key, keyType)] = ChangeType(entry.Value, valType);
-                            }
-                        }
-                        return dict;
-                    }
-                }
-            }
-
-            if (!type.GetTypeInfo().IsInterface && type.GetTypeInfo().IsGenericType)
-            {
-                var innerType = type.GetTypeInfo().GetGenericArguments()[0];
-                var innerValue = ChangeType(value, innerType);
-                return Activator.CreateInstance(type, innerValue);
-            }
-
-            if (value is byte[] && type == typeof(Guid))
-                return new Guid(value as byte[]);
-
-            if (value is string && type == typeof(Version))
-                return new Version(value as string);
-
-            if (value is byte && type == typeof(TimeSpan))
-                return new TimeSpan((byte)value);
-
-            if (value is string && type == typeof(TimeSpan))
-                return TimeSpan.Parse(value as string);
-
-            if (value is string && type == typeof(DateTime))
-                return DateTime.Parse(value as string);
-
-            if (value is bool && type == typeof(string))
-                return (bool)value ? bool.TrueString : bool.FalseString;
-
-            //if (value is JArray && type == typeof(string[]))
-            //    return (value as JArray).Select(x => x.ToString()).ToArray();
-
-            if(value is FilterConstant constant)
-            {
-                if (type == typeof(DateTime))
-                {
-                    if (Equals(FilterConstant.Null, value))
-                        return DateTime.MinValue;
-                    return value;
-                }
-                if (type == typeof(Guid) && constant == FilterConstant.Null)
-                {
-                    return Guid.Empty;
-                }
-                if (type == typeof(double))
-                {
-                    return value;
-                    //    if (Equals(FilterConstant.Zero, value))
-                    //        return (double)0;
-                }
-                if (type == typeof(int))
-                {
-                    return value;
-                    //    if (Equals(FilterConstant.Zero, value))
-                    //        return 0;
-                }
-            }
-
-            if (type == typeof(String))
-                return value?.ToString() ?? string.Empty;
-            
-            if (!(value is IConvertible))
-                return value;
-
-            return Convert.ChangeType(value, type);
-        }
-
-        [return: MaybeNull]
-        public static T ChangeType<T>(object? value)
-        {
-            var newValue = ChangeType(value, typeof(T));
-            if (newValue is T tValue)
-                return tValue;
-            return default;
-        }
-
-        public static string RandomHexString(int length)
-        {
-            var rdm = new Random((int)DateTime.Now.Ticks);
-            var hexValue = "";
-            int num;
-            for (var i = 0; i < length; i++)
-            {
-                num = rdm.Next(0, 16);
-                hexValue += num.ToString("X1");
-            }
-
-            return hexValue;
-        }
+        #region Properties
 
-        public static bool IsEmpty(this DateTime value)
+        public static decimal GetPropertySequence(Type type, string column)
         {
-            return value.Ticks <= DateTime.MinValue.AddHours(8).Ticks;
+            return DatabaseSchema.Property(type, column)?.PropertySequence() ?? 999;
         }
 
-        #region Properties
-
         public static string GetFullPropertyName<Type, TProperty>(Expression<Func<Type, TProperty>> exp, string separator)
         {
             try
@@ -1167,8 +1015,6 @@ namespace InABox.Core
             return (PropertyInfo)Exp.Member;
         }
 
-        #endregion
-
         public static bool IsCollection(PropertyInfo prop)
         {
             if (!typeof(string).Equals(prop.PropertyType))
@@ -1185,31 +1031,6 @@ namespace InABox.Core
                    exp.NodeType == ExpressionType.ConvertChecked;
         }
 
-        //public static Dictionary<String, Type> PropertyList(Type T, bool Public = true, bool Static = false)
-        //{
-        //	Dictionary<String, Type> properties = new Dictionary<String, Type>();
-        //	foreach (PropertyInfo prop in T.GetRuntimeProperties())
-        //	{
-        //		if ((prop.GetMethod.IsPublic == Public) && (prop.GetMethod.IsStatic == Static))
-        //		{
-        //			if (prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(Entity).GetTypeInfo()))
-        //			{
-        //				Dictionary<String, Type> props = PropertyList(prop.PropertyType);
-        //				foreach (String key in props.Keys)
-        //				{
-        //					properties[String.Format("{0}.{1}", prop.Name, key)] = props[key];
-        //				}
-        //			}
-        //			else if (!prop.PropertyType.Equals(typeof(Guid)))
-        //			{
-        //				properties[prop.Name] = prop.GetType();
-        //			}
-        //		}
-        //	}
-        //	return properties;
-        //}
-
-        
         /// <summary>
         ///     Get all the child properties of an object that match a given type
         /// </summary>
@@ -1263,6 +1084,221 @@ namespace InABox.Core
             return lists.ToArray();
         }
 
+        private static readonly ConcurrentDictionary<Type, IColumns> _columnscache = new ConcurrentDictionary<Type, IColumns>();
+
+        public static Columns<T> GetColumns<T>(Columns<T>? columns)
+            => (GetColumns(typeof(T), columns) as Columns<T>)!;
+        
+        /// <summary>
+        /// Ensures that <paramref name="columns"/> is not <see langword="null"/>, by setting it to <b>all</b> columns that are
+        /// writable, serializable and persistable if it is.<br/>
+        /// If <paramref name="columns"/> is not <see langword="null"/>, it is returned unchanged.
+        /// </summary>
+        public static IColumns GetColumns(Type T, IColumns? columns)
+        {
+            if (columns == null || columns.Count == 0)
+            {
+                if (!_columnscache.TryGetValue(T, out var result))
+                {
+                    result = Columns.None(T);
+                    foreach(var prop in DatabaseSchema.Properties(T))
+                    {
+                        if (prop is StandardProperty stdProp
+                            && (!stdProp.IsPersistable || !stdProp.IsSerializable || !stdProp.Property.CanWrite))
+                            continue;
+                        result.Add(prop);
+                    }
+                    _columnscache[T] = result;
+                }
+
+                return result;
+            }
+
+            return columns;
+        }
+
+        /// <summary>
+        /// Return a list of the names of the columns of <paramref name="T"/> which are serializable, persistable and writable (i.e., a
+        /// <see langword="null"/> set of columns), <i>and</i> that match the <paramref name="Predicate"/>.
+        /// </summary>
+        /// <param name="T"></param>
+        /// <param name="Predicate"></param>
+        /// <returns></returns>
+        public static List<string> GetColumnNames(Type T, Func<IProperty, bool> predicate)
+        {
+            var result = new List<string>();
+
+            foreach(var prop in DatabaseSchema.Properties(T))
+            {
+                if ((prop is StandardProperty stdProp && (!stdProp.IsPersistable || !stdProp.IsSerializable || !stdProp.Property.CanWrite))
+                    || !predicate(prop))
+                    continue;
+                result.Add(prop.Name);
+            }
+
+            return result;
+        }
+
+
+        #endregion
+
+        #region Type Utilities
+
+        public static object? ChangeType(object? value, Type type)
+        {
+            if ((value == null || value is DBNull) && type.GetTypeInfo().IsGenericType)
+                return Activator.CreateInstance(type);
+
+            if (value == null)
+                return null;
+
+            if (value is DBNull)
+                return null;
+
+            if (type == value.GetType())
+                return value;
+
+            if (type == typeof(bool))
+                return value.Equals(bool.TrueString) || value.Equals(1) || value.Equals("1");
+
+            if (type.GetTypeInfo().IsEnum)
+            {
+                if (value.GetType().IsArray)
+                    return (value as Array).Cast<object>().Select(x => Enum.ToObject(type, x)).ToArray();
+                if (value is string)
+                    return Enum.Parse(type, value as string);
+                if (value is IEnumerable<object> list)
+                    return list.Select(x => Enum.ToObject(type, x)).ToArray();
+                return Enum.ToObject(type, value);
+            }
+
+            if (value is string && type == typeof(Guid))
+                try
+                {
+                    return new Guid(value as string);
+                }
+                catch
+                {
+                    return Guid.Empty;
+                }
+
+            if (value is string && type == typeof(byte[]))
+                return Convert.FromBase64String(value as string);
+
+            if (value is IEnumerable objList)
+            {
+                if(type == typeof(Guid))
+                {
+                    return objList.Cast<object?>().Select(x => ChangeType<Guid>(x)).ToArray();
+                }
+                else if (type.IsArray)
+                {
+                    var elementType = type.GetElementType();
+                    var collection = value as ICollection<object?> ?? objList.Cast<object?>().ToList();
+
+                    var arr = Array.CreateInstance(elementType, collection.Count);
+                    var i = 0;
+                    foreach(var val in collection)
+                    {
+                        arr.SetValue(ChangeType(val, elementType), i);
+                        ++i;
+                    }
+                    return arr;
+                }
+                else if (type.IsGenericType)
+                {
+                    var typeDef = type.GetGenericTypeDefinition();
+                    if(typeDef == typeof(Dictionary<,>))
+                    {
+                        var dict = (Activator.CreateInstance(type) as IDictionary)!;
+                        var dictDef = type.GetSuperclassDefinition(typeof(Dictionary<,>))!;
+                        var keyType = dictDef.GenericTypeArguments[0];
+                        var valType = dictDef.GenericTypeArguments[1];
+                        if(value is IDictionary fromDict)
+                        {
+                            foreach(DictionaryEntry entry in fromDict)
+                            {
+                                dict[ChangeType(entry.Key, keyType)] = ChangeType(entry.Value, valType);
+                            }
+                        }
+                        return dict;
+                    }
+                }
+            }
+
+            if (!type.GetTypeInfo().IsInterface && type.GetTypeInfo().IsGenericType)
+            {
+                var innerType = type.GetTypeInfo().GetGenericArguments()[0];
+                var innerValue = ChangeType(value, innerType);
+                return Activator.CreateInstance(type, innerValue);
+            }
+
+            if (value is byte[] && type == typeof(Guid))
+                return new Guid(value as byte[]);
+
+            if (value is string && type == typeof(Version))
+                return new Version(value as string);
+
+            if (value is byte && type == typeof(TimeSpan))
+                return new TimeSpan((byte)value);
+
+            if (value is string && type == typeof(TimeSpan))
+                return TimeSpan.Parse(value as string);
+
+            if (value is string && type == typeof(DateTime))
+                return DateTime.Parse(value as string);
+
+            if (value is bool && type == typeof(string))
+                return (bool)value ? bool.TrueString : bool.FalseString;
+
+            //if (value is JArray && type == typeof(string[]))
+            //    return (value as JArray).Select(x => x.ToString()).ToArray();
+
+            if(value is FilterConstant constant)
+            {
+                if (type == typeof(DateTime))
+                {
+                    if (Equals(FilterConstant.Null, value))
+                        return DateTime.MinValue;
+                    return value;
+                }
+                if (type == typeof(Guid) && constant == FilterConstant.Null)
+                {
+                    return Guid.Empty;
+                }
+                if (type == typeof(double))
+                {
+                    return value;
+                    //    if (Equals(FilterConstant.Zero, value))
+                    //        return (double)0;
+                }
+                if (type == typeof(int))
+                {
+                    return value;
+                    //    if (Equals(FilterConstant.Zero, value))
+                    //        return 0;
+                }
+            }
+
+            if (type == typeof(String))
+                return value?.ToString() ?? string.Empty;
+            
+            if (!(value is IConvertible))
+                return value;
+
+            return Convert.ChangeType(value, type);
+        }
+
+        [return: MaybeNull]
+        public static T ChangeType<T>(object? value)
+        {
+            var newValue = ChangeType(value, typeof(T));
+            if (newValue is T tValue)
+                return tValue;
+            return default;
+        }
+
+
         /// <summary>
         ///     [ <c>public static object GetDefault(this Type type)</c> ]
         ///     <para></para>
@@ -1386,24 +1422,6 @@ namespace InABox.Core
         }
 
 
-        //public static TimeSpan Round(this TimeSpan timespan, TimeSpan interval)
-        //{
-
-        //    // The round number, here is a quarter... in seconds
-        //    double Round = interval.TotalSeconds;
-
-        //    // Count of round number in this total Seconds...
-        //    double CountRound = (timespan.TotalSeconds / Round);
-
-        //    // The main formula to calculate round time...
-        //    int Sec = (int)(Math.Truncate(CountRound + 0.5) * Round);
-
-        //    // Now show the result...
-        //    TimeSpan tRes = new TimeSpan(0, 0, Sec);
-
-        //    return tRes;
-        //}
-
         public static bool IsNumeric(this Type dataType)
         {
             return dataType.IsFloatingPoint() || dataType.IsOrdinal()
@@ -1438,6 +1456,33 @@ namespace InABox.Core
                    || dataType == typeof(sbyte);
         }
 
+        public static string GetCaption(this Enum enumValue, bool usedefault = true)
+        {
+            var attribute = enumValue.GetCustomAttribute<Caption>();
+            return attribute?.Text
+                ?? (usedefault ? enumValue.ToString() : "");
+        }
+
+        public static string GetCaption(this Type type, bool usedefault = true)
+        {
+            var attribute = type.GetCustomAttribute<Caption>();
+            var result = attribute != null
+                ? attribute.Text
+                : usedefault
+                    ? type.Name
+                    : "";
+            return result;
+        }
+        public static string? GetCaptionOrNull(this Type type, bool inherit = false)
+        {
+            return type.GetCustomAttribute<Caption>(inherit)?.Text;
+        }
+
+
+        #endregion
+
+        #region Miscellaneous
+
         //Extension method for MailMessage to save to a file on disk
         public static string GenerateEMLFile(this MailMessage message, bool addUnsentHeader = true)
         {
@@ -1476,197 +1521,9 @@ namespace InABox.Core
             return filename;
         }
 
-        public static Columns<T> GetColumns<T>(Columns<T>? columns)
-            => (GetColumns(typeof(T), columns) as Columns<T>)!;
-        
-        /// <summary>
-        /// Ensures that <paramref name="columns"/> is not <see langword="null"/>, by setting it to <b>all</b> columns that are
-        /// writable, serializable and persistable if it is.<br/>
-        /// If <paramref name="columns"/> is not <see langword="null"/>, it is returned unchanged.
-        /// </summary>
-        public static IColumns GetColumns(Type T, IColumns? columns)
-        {
-            if (columns == null || columns.Count == 0)
-            {
-                if (!_columnscache.TryGetValue(T, out var result))
-                {
-                    result = Columns.None(T);
-                    foreach(var prop in DatabaseSchema.Properties(T))
-                    {
-                        if (prop is StandardProperty stdProp
-                            && (!stdProp.IsPersistable || !stdProp.IsSerializable || !stdProp.Property.CanWrite))
-                            continue;
-                        result.Add(prop);
-                    }
-                    _columnscache[T] = result;
-                }
-
-                return result;
-            }
-
-            return columns;
-        }
-
-        /// <summary>
-        /// Return a list of the names of the columns of <paramref name="T"/> which are serializable, persistable and writable (i.e., a
-        /// <see langword="null"/> set of columns), <i>and</i> that match the <paramref name="Predicate"/>.
-        /// </summary>
-        /// <param name="T"></param>
-        /// <param name="Predicate"></param>
-        /// <returns></returns>
-        public static List<string> GetColumnNames(Type T, Func<IProperty, bool> predicate)
-        {
-            var result = new List<string>();
-
-            foreach(var prop in DatabaseSchema.Properties(T))
-            {
-                if ((prop is StandardProperty stdProp && (!stdProp.IsPersistable || !stdProp.IsSerializable || !stdProp.Property.CanWrite))
-                    || !predicate(prop))
-                    continue;
-                result.Add(prop.Name);
-            }
-
-            return result;
-        }
-
-        public static string TypedValueToString(object value)
-        {
-            var type = value.GetType();
-            var Culture = CultureInfo.CurrentCulture;
-
-            if (type == typeof(string))
-                return (string)value;
-
-            if (type == typeof(string[]))
-                return Serialization.Serialize(value);
-
-            if (IsNumeric(type))
-                return string.Format(Culture.NumberFormat, "{0}", value);
-
-            // TimeSpan - use constant pattern
-            if (type == typeof(TimeSpan))
-                return string.Format("{0:c}", value);
-
-            // DateTime - use round-trip pattern
-            if (type == typeof(DateTime))
-                return string.Format("{0:o}", value);
-
-            if (type == typeof(Guid))
-                return ((Guid)value).ToString();
-
-            var converter = TypeDescriptor.GetConverter(type);
-            if (converter != null && converter.CanConvertTo(typeof(string)))
-                return converter.ConvertToString(value);
-
-            throw new Exception(string.Format("Cannot Convert {0} to String", type.Name));
-        }
-
-        public static object? StringToTypedValue(string value, Type type)
-        {
-            var Culture = CultureInfo.CurrentCulture;
-
-            if (type == typeof(object) && string.IsNullOrEmpty(value))
-                return null;
-
-            if (type == typeof(string))
-                return value;
-
-            if (type == typeof(string[]))
-                return Serialization.Deserialize<string[]>(value);
-
-            if (type == typeof(sbyte))
-                return sbyte.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(byte))
-                return byte.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(ushort))
-                return ushort.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(uint))
-                return uint.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(ulong))
-                return ulong.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(short))
-                return short.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(int))
-                return int.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(long))
-                return long.Parse(value, NumberStyles.Integer, Culture);
-
-            if (type == typeof(float))
-                return float.Parse(value, NumberStyles.Any, Culture);
-
-            if (type == typeof(double))
-                return double.Parse(value, NumberStyles.Any, Culture);
-
-            if (type == typeof(decimal))
-                return decimal.Parse(value, NumberStyles.Any, Culture);
-
-            if (type == typeof(TimeSpan))
-                return TimeSpan.ParseExact(value, "c", null);
-
-            if (type == typeof(DateTime))
-                return DateTime.ParseExact(value, "o", null);
-
-            if (type == typeof(Guid))
-                return Guid.Parse(value);
-
-            var converter = TypeDescriptor.GetConverter(type);
-            if (converter != null && converter.CanConvertFrom(typeof(string)))
-                return converter.ConvertFromString(value);
-
-            throw new Exception(string.Format("Cannot Convert String to {0}", type.Name));
-        }
-
-        public static bool ContainsInheritedGenericType(this Type type, Type generic)
-        {
-            if (type == null)
-                return false;
-            if (type.GenericTypeArguments.Contains(generic))
-                return true;
-            if (type.BaseType == null)
-                return false;
-            return type.BaseType.ContainsInheritedGenericType(generic);
-        }
-
-        public static IEnumerable<Type> GetInheritedGenericTypeArguments(this Type type)
-        {
-            if (type == null)
-                return new Type[] { };
-            if (type.GetGenericArguments().Any())
-                return type.GenericTypeArguments;
-            if (type.BaseType == null)
-                return new Type[] { };
-            return type.BaseType.GetInheritedGenericTypeArguments();
-        }
-        //static readonly Regex StripQuotes = new Regex(@"&quot;", RegexOptions.Compiled);
-
-        public static string GetCaption(this Enum enumValue, bool usedefault = true)
-        {
-            var attribute = enumValue.GetCustomAttribute<Caption>();
-            return attribute?.Text
-                ?? (usedefault ? enumValue.ToString() : "");
-        }
+        #endregion
 
-        public static string GetCaption(this Type type, bool usedefault = true)
-        {
-            var attribute = type.GetCustomAttribute<Caption>();
-            var result = attribute != null
-                ? attribute.Text
-                : usedefault
-                    ? type.Name
-                    : "";
-            return result;
-        }
-        public static string? GetCaptionOrNull(this Type type, bool inherit = false)
-        {
-            return type.GetCustomAttribute<Caption>(inherit)?.Text;
-        }
+        #region Filesystem + Paths
 
         public static string SanitiseFileName(string filename)
         {
@@ -1690,11 +1547,6 @@ namespace InABox.Core
             return sanitisedNamePart;
         }
 
-        public static decimal GetPropertySequence(Type type, string column)
-        {
-            return DatabaseSchema.Property(type, column)?.PropertySequence() ?? 999;
-        }
-
         /// <summary>
         /// Gets the AppData folder for this application.
         /// </summary>
@@ -1744,11 +1596,9 @@ namespace InABox.Core
             return Path.Combine(GetPath(), string.Format("{0:yyyy-MM-dd}.log", DateTime.Today));
         }
 
-        public static string SplitCamelCase(this string value)
-        {
-            return string.Join(" ", Regex.Split(value, @"(?<!^)(?=[A-Z](?![A-Z]|$))")).Replace("  ", " ");
-        }
+        #endregion
 
+        #region Primitive Type Utilities
 
         public static bool IsEffectivelyEqual(this double a, double b, double threshold = 0.00001F)
         {
@@ -1764,12 +1614,45 @@ namespace InABox.Core
         {
             return b - a > threshold;
         }
-        
+
+        public static bool IsEmpty(this DateTime value)
+        {
+            return value.Ticks <= DateTime.MinValue.AddHours(8).Ticks;
+        }
+
+        public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
+        {
+            int diff = (7 + (dt.DayOfWeek - startOfWeek)) % 7;
+            return dt.AddDays(-1 * diff).Date;
+        }
+
+        public static DateTime StartOfMonth(this DateTime dt)
+        {
+            return new DateTime(dt.Year, dt.Month, 1);
+        }
+
+        public static DateTime StartOfYear(this DateTime dt)
+        {
+            return new DateTime(dt.Year, 1, 1);
+        }
+
+        private const decimal _hoursPerTick = 1.0M / TimeSpan.TicksPerHour;
+
+        public static decimal TotalHoursDecimal(this TimeSpan time)
+        {
+            return time.Ticks * _hoursPerTick;
+        }
+
+        #endregion
+
+        #region Entity Links
+
         public static bool IsEntityLinkValid<T, U>(this CoreRow arg, Expression<Func<T, U>> expression) where U : IEntityLink
         {
             var col = CoreUtils.GetFullPropertyName(expression, ".");
             return arg.Get<Guid>(col + ".ID") != Guid.Empty && arg.Get<Guid>(col + ".Deleted") == Guid.Empty;
         }
+
         /// <summary>
         /// Gets the ID of an entity link of an entity, doing a validity check (see <see cref="IsEntityLinkValid{T, U}(CoreRow, Expression{Func{T, U}})"/>)
         /// </summary>
@@ -1821,6 +1704,8 @@ namespace InABox.Core
             return filter.Filter.NotLinkValid();
         }
 
+        #endregion
+
         #region DeepClone Utility
 
         //public static object Clone(object from)
@@ -2368,6 +2253,8 @@ namespace InABox.Core
 
         #endregion
 
+        #region Logging
+
         public static string FormatException(Exception err)
         {
             var messages = new List<string>();
@@ -2385,6 +2272,8 @@ namespace InABox.Core
             Logger.Send(LogType.Error, userid, (extra != null ? $"{extra}: " : "") + CoreUtils.FormatException(err), transaction: logger?.Transaction);
         }
 
+        #endregion
+
         #region OneToMany Relationships
 
         public static Type? GetOneToMany(Type TMany, Type TOne)
@@ -2444,7 +2333,9 @@ namespace InABox.Core
             );
         }
         #endregion
-        
+
+        #region BaseObject Utilities
+
         /// <summary>
         /// Get a dictionary of values representing the current state of an entity
         /// </summary>
@@ -2519,23 +2410,8 @@ namespace InABox.Core
             MergeChanges(previous, current, result);
             return result;
         }
-        
-
-        public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
-        {
-            int diff = (7 + (dt.DayOfWeek - startOfWeek)) % 7;
-            return dt.AddDays(-1 * diff).Date;
-        }
-
-        public static DateTime StartOfMonth(this DateTime dt)
-        {
-            return new DateTime(dt.Year, dt.Month, 1);
-        }
 
-        public static DateTime StartOfYear(this DateTime dt)
-        {
-            return new DateTime(dt.Year, 1, 1);
-        }
+        #endregion
 
         #region String Utilities
 
@@ -2650,6 +2526,25 @@ namespace InABox.Core
             return value;
         }
 
+        public static string RandomHexString(int length)
+        {
+            var rdm = new Random((int)DateTime.Now.Ticks);
+            var hexValue = "";
+            int num;
+            for (var i = 0; i < length; i++)
+            {
+                num = rdm.Next(0, 16);
+                hexValue += num.ToString("X1");
+            }
+
+            return hexValue;
+        }
+
+        public static string SplitCamelCase(this string value)
+        {
+            return string.Join(" ", Regex.Split(value, @"(?<!^)(?=[A-Z](?![A-Z]|$))")).Replace("  ", " ");
+        }
+
         #endregion
 
         #region IEnumerable Utilities