mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-26 22:48:34 +01:00
277 lines
14 KiB
C#
277 lines
14 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using System.Numerics;
|
||
|
using System.Reflection;
|
||
|
using MLEM.Misc;
|
||
|
|
||
|
namespace MLEM.Data {
|
||
|
/// <summary>
|
||
|
/// 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 <see cref="BigInteger"/> as its underlying type, allowing for an arbitrary number of enum values to be created, even when a <see cref="FlagsAttribute"/>-like structure is used that would only allow for up to 64 values in a regular enum.
|
||
|
/// All enum operations including <see cref="And{T}"/>, <see cref="Or{T}"/>, <see cref="Xor{T}"/> and <see cref="Neg{T}"/> are supported and can be implemented in derived classes using operator overloads.
|
||
|
/// To create a custom dynamic enum, simply create a class that extends <see cref="DynamicEnum"/>. New values can then be added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// To include enum-like operator overloads in a dynamic enum named MyEnum, the following code can be used:
|
||
|
/// <code>
|
||
|
/// 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);
|
||
|
/// </code>
|
||
|
/// </remarks>
|
||
|
public abstract class DynamicEnum : GenericDataHolder {
|
||
|
|
||
|
private static readonly Dictionary<Type, Dictionary<BigInteger, DynamicEnum>> Values = new Dictionary<Type, Dictionary<BigInteger, DynamicEnum>>();
|
||
|
private static readonly Dictionary<Type, Dictionary<BigInteger, DynamicEnum>> FlagCache = new Dictionary<Type, Dictionary<BigInteger, DynamicEnum>>();
|
||
|
private static readonly Dictionary<string, DynamicEnum> ParseCache = new Dictionary<string, DynamicEnum>();
|
||
|
|
||
|
private readonly BigInteger value;
|
||
|
private string name;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new dynamic enum instance.
|
||
|
/// This constructor is protected as it is only invoked via reflection.
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the enum value</param>
|
||
|
/// <param name="value">The value</param>
|
||
|
protected DynamicEnum(string name, BigInteger value) {
|
||
|
this.value = value;
|
||
|
this.name = name;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if this enum value has the given <see cref="DynamicEnum"/> flag on it.
|
||
|
/// This operation is equivalent to <see cref="Enum.HasFlag"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="flag">The flag to query</param>
|
||
|
/// <returns>True if the flag is present, false otherwise</returns>
|
||
|
public bool HasFlag(DynamicEnum flag) {
|
||
|
return (GetValue(this) & GetValue(flag)) == GetValue(flag);
|
||
|
}
|
||
|
|
||
|
/// <inheritdoc />
|
||
|
public override string ToString() {
|
||
|
if (this.name == null) {
|
||
|
var included = new List<DynamicEnum>();
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a new enum value to the given enum type <typeparamref name="T"/>
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the enum value to add</param>
|
||
|
/// <param name="value">The value to add</param>
|
||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||
|
/// <returns>The newly created enum value</returns>
|
||
|
/// <exception cref="ArgumentException">Thrown if the name or value passed are already present</exception>
|
||
|
public static T Add<T>(string name, BigInteger value) where T : DynamicEnum {
|
||
|
if (!Values.TryGetValue(typeof(T), out var dict)) {
|
||
|
dict = new Dictionary<BigInteger, DynamicEnum>();
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a new enum value to the given enum type <typeparamref name="T"/>.
|
||
|
/// This method differs from <see cref="Add{T}"/> 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.
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the enum value to add</param>
|
||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||
|
/// <returns>The newly created enum value</returns>
|
||
|
public static T AddValue<T>(string name) where T : DynamicEnum {
|
||
|
BigInteger value = 0;
|
||
|
if (Values.TryGetValue(typeof(T), out var defined)) {
|
||
|
while (defined.ContainsKey(value))
|
||
|
value++;
|
||
|
}
|
||
|
return Add<T>(name, value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a new flag enum value to the given enum type <typeparamref name="T"/>.
|
||
|
/// This method differs from <see cref="Add{T}"/> 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 <see cref="FlagsAttribute"/>-like behavior.
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the enum value to add</param>
|
||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||
|
/// <returns>The newly created enum value</returns>
|
||
|
public static T AddFlag<T>(string name) where T : DynamicEnum {
|
||
|
BigInteger value = 0;
|
||
|
if (Values.TryGetValue(typeof(T), out var defined)) {
|
||
|
while (defined.ContainsKey(value))
|
||
|
value <<= 1;
|
||
|
}
|
||
|
return Add<T>(name, value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type <typeparamref name="T"/>.
|
||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">The type whose values to get</typeparam>
|
||
|
/// <returns>The defined values for the given type</returns>
|
||
|
public static IEnumerable<T> GetValues<T>() where T : DynamicEnum {
|
||
|
return GetValues(typeof(T)).Cast<T>();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type <paramref name="type"/>.
|
||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="type">The type whose values to get</param>
|
||
|
/// <returns>The defined values for the given type</returns>
|
||
|
public static IEnumerable<DynamicEnum> GetValues(Type type) {
|
||
|
return Values.TryGetValue(type, out var ret) ? ret.Values : Enumerable.Empty<DynamicEnum>();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the bitwise OR (|) combination of the two dynamic enum values
|
||
|
/// </summary>
|
||
|
/// <param name="left">The left value</param>
|
||
|
/// <param name="right">The right value</param>
|
||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||
|
/// <returns>The bitwise OR (|) combination</returns>
|
||
|
public static T Or<T>(T left, T right) where T : DynamicEnum {
|
||
|
return GetEnumValue<T>(GetValue(left) | GetValue(right));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the bitwise AND (&) combination of the two dynamic enum values
|
||
|
/// </summary>
|
||
|
/// <param name="left">The left value</param>
|
||
|
/// <param name="right">The right value</param>
|
||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||
|
/// <returns>The bitwise AND (&) combination</returns>
|
||
|
public static T And<T>(T left, T right) where T : DynamicEnum {
|
||
|
return GetEnumValue<T>(GetValue(left) & GetValue(right));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the bitwise XOR (^) combination of the two dynamic enum values
|
||
|
/// </summary>
|
||
|
/// <param name="left">The left value</param>
|
||
|
/// <param name="right">The right value</param>
|
||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||
|
/// <returns>The bitwise XOR (^) combination</returns>
|
||
|
public static T Xor<T>(T left, T right) where T : DynamicEnum {
|
||
|
return GetEnumValue<T>(GetValue(left) ^ GetValue(right));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the bitwise NEG (~) combination of the dynamic enum value
|
||
|
/// </summary>
|
||
|
/// <param name="value">The value</param>
|
||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||
|
/// <returns>The bitwise NEG (~) value</returns>
|
||
|
public static T Neg<T>(T value) where T : DynamicEnum {
|
||
|
return GetEnumValue<T>(~GetValue(value));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the <see cref="BigInteger"/> representation of the given dynamic enum value
|
||
|
/// </summary>
|
||
|
/// <param name="value">The value whose number representation to get</param>
|
||
|
/// <returns>The value's number representation</returns>
|
||
|
public static BigInteger GetValue(DynamicEnum value) {
|
||
|
return value?.value ?? 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the defined or combined dynamic enum value for the given <see cref="BigInteger"/> representation
|
||
|
/// </summary>
|
||
|
/// <param name="value">The value whose dynamic enum value to get</param>
|
||
|
/// <typeparam name="T">The type that the returned dynamic enum should have</typeparam>
|
||
|
/// <returns>The defined or combined dynamic enum value</returns>
|
||
|
public static T GetEnumValue<T>(BigInteger value) where T : DynamicEnum {
|
||
|
return (T) GetEnumValue(typeof(T), value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the defined or combined dynamic enum value for the given <see cref="BigInteger"/> representation
|
||
|
/// </summary>
|
||
|
/// <param name="type">The type that the returned dynamic enum should have</param>
|
||
|
/// <param name="value">The value whose dynamic enum value to get</param>
|
||
|
/// <returns>The defined or combined dynamic enum value</returns>
|
||
|
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<BigInteger, DynamicEnum>();
|
||
|
FlagCache.Add(type, cache);
|
||
|
}
|
||
|
if (!cache.TryGetValue(value, out var combined)) {
|
||
|
combined = Construct(type, null, value);
|
||
|
cache.Add(value, combined);
|
||
|
}
|
||
|
return combined;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parses the given <see cref="string"/> 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.
|
||
|
/// </summary>
|
||
|
/// <param name="strg">The string to parse into a dynamic enum value</param>
|
||
|
/// <typeparam name="T">The type of the dynamic enum value to parse</typeparam>
|
||
|
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||
|
public static T Parse<T>(string strg) where T : DynamicEnum {
|
||
|
return (T) Parse(typeof(T), strg);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parses the given <see cref="string"/> 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. /// </summary>
|
||
|
/// <param name="type">The type of the dynamic enum value to parse</param>
|
||
|
/// <param name="strg">The string to parse into a dynamic enum value</param>
|
||
|
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||
|
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});
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|