using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; using MLEM.Data.Json; using Newtonsoft.Json; namespace MLEM.Data { /// /// A dynamic enum is a class that represents enum-like single-instance value behavior with additional capabilities, including dynamic addition of new arbitrary values. /// A dynamic enum uses as its underlying type, allowing for an arbitrary number of enum values to be created, even when a -like structure is used that would only allow for up to 64 values in a regular enum. /// All enum operations including , , and are supported and can be implemented in derived classes using operator overloads. /// To create a custom dynamic enum, simply create a class that extends . New values can then be added using , or . /// /// This class, and its entire concept, are extremely terrible. If you intend on using this, there's probably at least one better solution available. /// Though if, for some weird reason, you need a way to have more than 64 distinct flags, this is a pretty good solution. /// /// /// To include enum-like operator overloads in a dynamic enum named MyEnum, the following code can be used: /// /// public static implicit operator BigInteger(MyEnum value) => GetValue(value); /// public static implicit operator MyEnum(BigInteger value) => GetEnumValue<MyEnum>(value); /// public static MyEnum operator |(MyEnum left, MyEnum right) => Or(left, right); /// public static MyEnum operator &(MyEnum left, MyEnum right) => And(left, right); /// public static MyEnum operator ^(MyEnum left, MyEnum right) => Xor(left, right); /// public static MyEnum operator ~(MyEnum value) => Neg(value); /// /// [JsonConverter(typeof(DynamicEnumConverter))] public abstract class DynamicEnum { private static readonly Dictionary Storages = new Dictionary(); private readonly BigInteger value; private Dictionary allFlagsCache; private Dictionary anyFlagsCache; private string name; /// /// Creates a new dynamic enum instance. /// This constructor is protected as it is only invoked via reflection. /// /// The name of the enum value /// The value protected DynamicEnum(string name, BigInteger value) { this.value = value; this.name = name; } /// /// Returns true if this enum value has ALL of the given flags on it. /// This operation is equivalent to . /// /// /// The flags to query /// True if all of the flags are present, false otherwise public bool HasFlag(DynamicEnum flags) { if (this.allFlagsCache == null) this.allFlagsCache = new Dictionary(); if (!this.allFlagsCache.TryGetValue(flags, out var ret)) { ret = (GetValue(this) & GetValue(flags)) == GetValue(flags); this.allFlagsCache.Add(flags, ret); } return ret; } /// /// Returns true if this enum value has ANY of the given flags on it /// /// /// The flags to query /// True if one of the flags is present, false otherwise public bool HasAnyFlag(DynamicEnum flags) { if (this.anyFlagsCache == null) this.anyFlagsCache = new Dictionary(); if (!this.anyFlagsCache.TryGetValue(flags, out var ret)) { ret = (GetValue(this) & GetValue(flags)) != 0; this.anyFlagsCache.Add(flags, ret); } return ret; } /// Returns a string that represents the current object. /// A string that represents the current object. public override string ToString() { if (this.name == null) { var included = new List(); if (GetValue(this) != 0) { foreach (var v in GetValues(this.GetType())) { if (this.HasFlag(v) && GetValue(v) != 0) included.Add(v); } } this.name = included.Count > 0 ? string.Join(" | ", included) : GetValue(this).ToString(); } return this.name; } /// /// Adds a new enum value to the given enum type /// /// The name of the enum value to add /// The value to add /// The type to add this value to /// The newly created enum value /// Thrown if the name or value passed are already present public static T Add(string name, BigInteger value) where T : DynamicEnum { var storage = GetStorage(typeof(T)); // cached parsed values and names might be incomplete with new values storage.ClearCaches(); if (storage.Values.ContainsKey(value)) throw new ArgumentException($"Duplicate value {value}", nameof(value)); foreach (var v in storage.Values.Values) { if (v.name == name) throw new ArgumentException($"Duplicate name {name}", nameof(name)); } var ret = Construct(typeof(T), name, value); storage.Values.Add(value, ret); return (T) ret; } /// /// Adds a new enum value to the given enum type . /// This method differs from in that it automatically determines a value. /// The value determined will be the next free number in a sequence, which represents the default behavior in an enum if enum values are not explicitly numbered. /// /// The name of the enum value to add /// The type to add this value to /// The newly created enum value public static T AddValue(string name) where T : DynamicEnum { BigInteger value = 0; while (GetStorage(typeof(T)).Values.ContainsKey(value)) value++; return Add(name, value); } /// /// Adds a new flag enum value to the given enum type . /// This method differs from in that it automatically determines a value. /// The value determined will be the next free power of two, allowing enum values to be combined using bitwise operations to create -like behavior. /// /// The name of the enum value to add /// The type to add this value to /// The newly created enum value public static T AddFlag(string name) where T : DynamicEnum { BigInteger value = 1; while (GetStorage(typeof(T)).Values.ContainsKey(value)) value <<= 1; return Add(name, value); } /// /// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type . /// A value counts as explicitly defined if it has been added using , or . /// /// The type whose values to get /// The defined values for the given type public static IEnumerable GetValues() where T : DynamicEnum { return GetValues(typeof(T)).Cast(); } /// /// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type . /// A value counts as explicitly defined if it has been added using , or . /// /// The type whose values to get /// The defined values for the given type public static IEnumerable GetValues(Type type) { return GetStorage(type).Values.Values; } /// /// Returns the bitwise OR (|) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise OR (|) combination public static T Or(T left, T right) where T : DynamicEnum { var cache = GetStorage(typeof(T)).OrCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = GetEnumValue(GetValue(left) | GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise AND (&) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise AND (&) combination public static T And(T left, T right) where T : DynamicEnum { var cache = GetStorage(typeof(T)).AndCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = GetEnumValue(GetValue(left) & GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise XOR (^) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise XOR (^) combination public static T Xor(T left, T right) where T : DynamicEnum { var cache = GetStorage(typeof(T)).XorCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = GetEnumValue(GetValue(left) ^ GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise NEG (~) combination of the dynamic enum value /// /// The value /// The type of the values /// The bitwise NEG (~) value public static T Neg(T value) where T : DynamicEnum { var cache = GetStorage(typeof(T)).NegCache; if (!cache.TryGetValue(value, out var ret)) { ret = GetEnumValue(~GetValue(value)); cache.Add(value, ret); } return (T) ret; } /// /// Returns the representation of the given dynamic enum value /// /// The value whose number representation to get /// The value's number representation public static BigInteger GetValue(DynamicEnum value) { return value?.value ?? 0; } /// /// Returns the defined or combined dynamic enum value for the given representation /// /// The value whose dynamic enum value to get /// The type that the returned dynamic enum should have /// The defined or combined dynamic enum value public static T GetEnumValue(BigInteger value) where T : DynamicEnum { return (T) GetEnumValue(typeof(T), value); } /// /// Returns the defined or combined dynamic enum value for the given representation /// /// The type that the returned dynamic enum should have /// The value whose dynamic enum value to get /// The defined or combined dynamic enum value public static DynamicEnum GetEnumValue(Type type, BigInteger value) { var storage = GetStorage(type); // get the defined value if it exists if (storage.Values.TryGetValue(value, out var defined)) return defined; // otherwise, cache the combined value if (!storage.FlagCache.TryGetValue(value, out var combined)) { combined = Construct(type, null, value); storage.FlagCache.Add(value, combined); } return combined; } /// /// Parses the given into a dynamic enum value and returns the result. /// This method supports defined enum values as well as values combined using the pipe (|) character and any number of spaces. /// If no enum value can be parsed, null is returned. /// /// The string to parse into a dynamic enum value /// The type of the dynamic enum value to parse /// The parsed enum value, or null if parsing fails public static T Parse(string strg) where T : DynamicEnum { return (T) Parse(typeof(T), strg); } /// /// Parses the given into a dynamic enum value and returns the result. /// This method supports defined enum values as well as values combined using the pipe (|) character and any number of spaces. /// If no enum value can be parsed, null is returned. /// /// The type of the dynamic enum value to parse /// The string to parse into a dynamic enum value /// The parsed enum value, or null if parsing fails public static DynamicEnum Parse(Type type, string strg) { var cache = GetStorage(type).ParseCache; if (!cache.TryGetValue(strg, out var cached)) { BigInteger? accum = null; foreach (var val in strg.Split('|')) { foreach (var defined in GetValues(type)) { if (defined.name == val.Trim()) { accum = (accum ?? 0) | GetValue(defined); break; } } } if (accum != null) cached = GetEnumValue(type, accum.Value); cache.Add(strg, cached); } return cached; } private static Storage GetStorage(Type type) { if (!Storages.TryGetValue(type, out var storage)) { storage = new Storage(); Storages.Add(type, storage); } return storage; } private static DynamicEnum Construct(Type type, string name, BigInteger value) { return (DynamicEnum) Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new object[] {name, value}, CultureInfo.InvariantCulture); } private class Storage { public readonly Dictionary Values = new Dictionary(); public readonly Dictionary FlagCache = new Dictionary(); public readonly Dictionary ParseCache = new Dictionary(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> OrCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> AndCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> XorCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary NegCache = new Dictionary(); public void ClearCaches() { this.FlagCache.Clear(); this.ParseCache.Clear(); this.OrCache.Clear(); this.AndCache.Clear(); this.XorCache.Clear(); this.NegCache.Clear(); } } } }