using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace InABox.Core { public static class DatabaseSchema { // {className: {propertyName: property}} private static Dictionary> _Properties { get; } = new Dictionary>(); /*private static BaseEditor GetPropertyEditor(Type type, PropertyInfo property, string propertyName) { BaseEditor editor = new NullEditor(); var parts = propertyName.Split('.'); var caption = ""; var page = ""; int? sequence = null; for (var i = 0; i < parts.Length; i++) { var column = string.Join(".", parts.Take(i + 1)); var prop = CoreUtils.GetProperty(type, column); if (column.Equals(propertyName)) { editor = property.GetEditor() ?? new NullEditor(); } else { var pedit = prop.GetEditor(); if (pedit is NullEditor) return pedit; } editor = editor == null ? new NullEditor() : (editor.Clone() as BaseEditor)!; var capattr = prop.GetCustomAttribute(); var subcap = capattr != null ? capattr.Text : parts[i]; var path = capattr == null || capattr.IncludePath; if (!string.IsNullOrWhiteSpace(subcap)) caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap); if (string.IsNullOrWhiteSpace(page)) { var pageattr = prop.GetCustomAttribute(); if (pageattr != null) { page = pageattr.Page; sequence = pageattr.Sequence; } } } editor.Caption = caption; editor.Page = page; editor.EditorSequence = sequence ?? 999; return editor; }*/ private static void RegisterProperties(Type master, Type type, string prefix, StandardProperty? parent) { try { var classname = master.EntityName(); var properties = CoreUtils.PropertyList( type, x => !x.PropertyType.IsInterface && (x.DeclaringType.IsSubclassOf(typeof(BaseObject)) || x.DeclaringType.IsSubclassOf(typeof(BaseEditor))) ); //.OrderBy(x=>x.Name); var classProps = _Properties.GetValueOrDefault(classname); foreach (var prop in properties) { var name = string.IsNullOrWhiteSpace(prefix) ? prop.Name : string.Format("{0}.{1}", prefix, prop.Name); var p = classProps?.GetValueOrDefault(name); //.ToArray().FirstOrDefault(x => x.Class == classname && x.Name == name); if (p == null) { var isstatic = prop.GetAccessors(true)[0].IsStatic; if (!isstatic) { BaseEditor? editor; if (parent != null && parent.HasEditor && parent.Editor is NullEditor) { editor = parent.Editor; } else { editor = prop.GetEditor(); } var captionAttr = prop.GetCustomAttribute(); var subCaption = captionAttr != null ? captionAttr.Text : prop.Name; var path = captionAttr == null || captionAttr.IncludePath; // If no caption attribute, we should always include the path var caption = parent?.Caption ?? ""; // We default to the parent caption if subCaption doesn't exist if (!string.IsNullOrWhiteSpace(subCaption)) { if (!string.IsNullOrWhiteSpace(caption) && path) { caption = $"{caption} {subCaption}"; } else { caption = subCaption; } } // Once the parent page has been found, this property is cemented to that page - it cannot change page to its parent // The same goes for sequence var page = parent?.Page; var sequence = parent?.Sequence; if (string.IsNullOrWhiteSpace(page)) { var sequenceAttribute = prop.GetCustomAttribute(); if (sequenceAttribute != null) { page = sequenceAttribute.Page; sequence = sequenceAttribute.Sequence; } } editor = editor?.Clone() as BaseEditor; if (editor != null) { editor.Page = page; editor.Caption = caption; editor.EditorSequence = (int)(sequence ?? 999); } bool required = false; if (parent == null || parent.Required) { required = prop.GetCustomAttribute() != null; } LoggablePropertyAttribute? loggable = null; if (parent == null || parent.Loggable != null) { loggable = prop.GetCustomAttribute(); } var newProperty = new StandardProperty { _class = master, //Class = classname, Name = name, PropertyType = prop.PropertyType, Editor = editor ?? new NullEditor(), HasEditor = editor != null, Caption = caption, Sequence = sequence ?? 999, Page = page ?? "", Required = required, Loggable = loggable, Parent = parent, Property = prop }; if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) || prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)) || prop.PropertyType.Equals(typeof(BaseEditor)) || prop.PropertyType.IsSubclassOf(typeof(BaseEditor))) { RegisterProperties(master, prop.PropertyType, name, newProperty); } else { RegisterProperty(newProperty); } } } } if (type.IsSubclassOf(typeof(BaseObject))) //if ((type.BaseType != null) && (type.BaseType != typeof(BaseObject))) RegisterProperties(master, type.BaseType, prefix, parent); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } private static void RegisterProperties(Type type) { RegisterProperties(type, type, "", null); } public static object? DefaultValue(Type type) { if (type.IsValueType) return Activator.CreateInstance(type); if (type.Equals(typeof(string))) return ""; return null; } private static readonly object _updatelock = new object(); public static void RegisterProperty(IProperty entry) { lock (_updatelock) { if (!_Properties.ContainsKey(entry.Class)) _Properties[entry.Class] = new Dictionary(); _Properties[entry.Class][entry.Name] = entry; //var dict = _Properties.GetOrAdd(entry.Class, className => new Dictionary()); //dict.TryAdd(entry.Name, entry); } } public static void Load(CustomProperty[] customproperties) { foreach (var prop in customproperties) RegisterProperty(prop); } private static void CheckProperties(Type type) { var entityName = type.EntityName(); if (type.IsSubclassOf(typeof(BaseObject)) && _Properties.GetValueOrDefault(entityName)?.Any(x => x.Value is StandardProperty) != true) RegisterProperties(type); } public static IEnumerable Properties(Type type) { CheckProperties(type); var entityName = type.EntityName(); return _Properties.GetValueOrDefault(entityName)?.Select(x => x.Value) ?? Array.Empty(); } public static IProperty? Property(Type type, string name) { CheckProperties(type); var entityName = type.EntityName(); //IProperty prop = _Properties.ToArray().FirstOrDefault(x => (x.Class.Equals(type.EntityName())) && (x.Name.Equals(name))); var prop = _Properties.GetValueOrDefault(entityName)?.GetValueOrDefault(name); // Walk up the inheritance tree, see if an ancestor has this property if (prop == null && type.BaseType != null) prop = Property(type.BaseType, name); return prop; } public static void InitializeObject(TObject entity) where TObject : BaseObject { entity.UserProperties.Clear(); var props = Properties(entity.GetType()).Where(x => x is CustomProperty).ToArray(); foreach (var field in props) entity.UserProperties[field.Name] = DefaultValue(field.PropertyType); } } }