using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Reflection; using MLEM.Data.Json; using MLEM.Misc; 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 . /// /// /// 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 : GenericDataHolder { private static readonly Dictionary> Values = new Dictionary>(); private static readonly Dictionary> FlagCache = new Dictionary>(); private static readonly Dictionary ParseCache = new Dictionary(); private readonly BigInteger value; 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 the given flag on it. /// This operation is equivalent to . /// /// The flag to query /// True if the flag is present, false otherwise public bool HasFlag(DynamicEnum flag) { return (GetValue(this) & GetValue(flag)) == GetValue(flag); } /// 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 { if (!Values.TryGetValue(typeof(T), out var dict)) { dict = new Dictionary(); Values.Add(typeof(T), dict); } if (dict.ContainsKey(value)) throw new ArgumentException($"Duplicate value {value}", nameof(value)); if (dict.Values.Any(v => v.name == name)) throw new ArgumentException($"Duplicate name {name}", nameof(name)); var ret = Construct(typeof(T), name, value); dict.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; if (Values.TryGetValue(typeof(T), out var defined)) { while (defined.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 = 0; if (Values.TryGetValue(typeof(T), out var defined)) { while (defined.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 Values.TryGetValue(type, out var ret) ? ret.Values : Enumerable.Empty(); } /// /// 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 { return GetEnumValue(GetValue(left) | GetValue(right)); } /// /// 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 { return GetEnumValue(GetValue(left) & GetValue(right)); } /// /// 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 { return GetEnumValue(GetValue(left) ^ GetValue(right)); } /// /// 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 { return GetEnumValue(~GetValue(value)); } /// /// 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) { // get the defined value if it exists if (Values.TryGetValue(type, out var values) && values.TryGetValue(value, out var defined)) return defined; // otherwise, cache the combined value if (!FlagCache.TryGetValue(type, out var cache)) { cache = new Dictionary(); FlagCache.Add(type, cache); } if (!cache.TryGetValue(value, out var combined)) { combined = Construct(type, null, value); cache.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) { if (!ParseCache.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); ParseCache.Add(strg, cached); } return cached; } private static DynamicEnum Construct(Type type, string name, BigInteger value) { var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] {typeof(string), typeof(BigInteger)}, null); return (DynamicEnum) constructor.Invoke(new object[] {name, value}); } } }