From fda4097aa5ded954e6113dad9fc002749ecdfe73 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 11 Jun 2021 20:05:32 +0200 Subject: [PATCH] reverted the last commit, and invalidated DynamicEnum caches when new values are added --- MLEM.Data/DynamicEnum.cs | 287 +++++++++++++++++++++++++ MLEM.Data/Json/DynamicEnumConverter.cs | 19 ++ 2 files changed, 306 insertions(+) create mode 100644 MLEM.Data/DynamicEnum.cs create mode 100644 MLEM.Data/Json/DynamicEnumConverter.cs diff --git a/MLEM.Data/DynamicEnum.cs b/MLEM.Data/DynamicEnum.cs new file mode 100644 index 0000000..58aa6f4 --- /dev/null +++ b/MLEM.Data/DynamicEnum.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +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 . + /// + /// + /// 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> 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); + } + + // cached parsed values and names might be incomplete with new values + FlagCache.Remove(typeof(T)); + ParseCache.Remove(typeof(T)); + + 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(type, out var cache)) { + cache = new Dictionary(); + ParseCache.Add(type, cache); + } + 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 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}); + } + + } +} \ No newline at end of file diff --git a/MLEM.Data/Json/DynamicEnumConverter.cs b/MLEM.Data/Json/DynamicEnumConverter.cs new file mode 100644 index 0000000..01f2d21 --- /dev/null +++ b/MLEM.Data/Json/DynamicEnumConverter.cs @@ -0,0 +1,19 @@ +using System; +using Newtonsoft.Json; + +namespace MLEM.Data.Json { + /// + public class DynamicEnumConverter : JsonConverter { + + /// + public override void WriteJson(JsonWriter writer, DynamicEnum value, JsonSerializer serializer) { + writer.WriteValue(value.ToString()); + } + + /// + public override DynamicEnum ReadJson(JsonReader reader, Type objectType, DynamicEnum existingValue, bool hasExistingValue, JsonSerializer serializer) { + return DynamicEnum.Parse(objectType, reader.Value.ToString()); + } + + } +} \ No newline at end of file