using System; using System.Collections.Generic; using System.Linq; using ICD.Common.Properties; using ICD.Common.Utils.Extensions; #if SIMPLSHARP using Crestron.SimplSharp.Reflection; #else using System.Reflection; #endif namespace ICD.Common.Utils { public static class EnumUtils { private static readonly Dictionary s_EnumValuesCache; private static readonly Dictionary> s_EnumFlagsCache; /// /// Static constructor. /// static EnumUtils() { s_EnumValuesCache = new Dictionary(); s_EnumFlagsCache = new Dictionary>(); } #region Validation /// /// Returns true if the given type is an enum. /// /// public static bool IsEnumType() { return IsEnumType(typeof(T)); } /// /// Returns true if the given type is an enum. /// /// private static bool IsEnumType(Type type) { if (type == null) throw new ArgumentNullException("type"); return type #if !SIMPLSHARP .GetTypeInfo() #endif .IsEnum || type.IsAssignableTo(typeof(Enum)); } /// /// Returns true if the given value is an enum. /// /// public static bool IsEnum(T value) { // ReSharper disable once CompareNonConstrainedGenericWithNull return value != null && IsEnumType(value.GetType()); } /// /// Returns true if the given enum type has the Flags attribute set. /// /// /// public static bool IsFlagsEnum() { return IsFlagsEnum(typeof(T)); } /// /// Returns true if the given enum type has the Flags attribute set. /// /// public static bool IsFlagsEnum(Type type) { if (type == null) throw new ArgumentNullException("type"); return type #if !SIMPLSHARP .GetTypeInfo() #endif .IsDefined(typeof(FlagsAttribute), false); } /// /// Returns true if the given value is defined as part of the given enum type. /// /// /// /// public static bool IsDefined(T value) where T : struct, IConvertible { if (!IsEnumType()) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); T all = GetFlagsAllValue(); return HasFlags(all, value); } /// /// Returns true if the given value is defined as part of the given enum type. /// /// /// /// public static bool IsDefined(Type type, int value) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new InvalidOperationException(string.Format("{0} is not an enum", type.Name)); int all = GetFlagsAllValue(type); return HasFlags(all, value); } #endregion #region Values /// /// Gets the values from an enumeration. /// /// /// public static IEnumerable GetValues() where T : struct, IConvertible { Type type = typeof(T); // Reflection is slow and this method is called a lot, so we cache the results. object cache; if (!s_EnumValuesCache.TryGetValue(type, out cache)) { cache = GetValuesUncached().ToArray(); s_EnumValuesCache[type] = cache; } return cache as T[]; } /// /// Gets the values from an enumeration without performing any caching. This is slow because of reflection. /// /// public static IEnumerable GetValues(Type type) { if (type == null) throw new ArgumentNullException("type"); return GetValuesUncached(type); } /// /// Gets the values from an enumeration without performing any caching. This is slow because of reflection. /// /// private static IEnumerable GetValuesUncached() where T : struct, IConvertible { return GetValuesUncached(typeof(T)).Select(i => (T)(object)i); } /// /// Gets the values from an enumeration without performing any caching. This is slow because of reflection. /// /// private static IEnumerable GetValuesUncached(Type type) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new InvalidOperationException(string.Format("{0} is not an enum", type.Name)); return type #if SIMPLSHARP .GetCType() #else .GetTypeInfo() #endif .GetFields(BindingFlags.Static | BindingFlags.Public) .Select(x => x.GetValue(null)) .Cast(); } /// /// Gets the values from an enumeration except the 0 value. /// /// /// public static IEnumerable GetValuesExceptNone() where T : struct, IConvertible { return GetFlagsExceptNone(); } /// /// Gets the values from an enumeration except the 0 value without performing any caching. This is slow because of reflection. /// /// public static IEnumerable GetValuesExceptNone(Type type) { if (type == null) throw new ArgumentNullException("type"); return GetValues(type).Except(0); } #endregion #region Flags /// /// Excludes b from a. /// /// /// /// public static T ExcludeFlags(T a, T b) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); int aInt = (int)(object)a; int bInt = (int)(object)b; return (T)(object)ExcludeFlags(aInt, bInt); } /// /// Excludes b from a. /// /// /// /// public static int ExcludeFlags(int a, int b) { return a & ~b; } /// /// Includes a and b. /// /// /// /// public static T IncludeFlags(T a, T b) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); int aInt = (int)(object)a; int bInt = (int)(object)b; return (T)(object)IncludeFlags(aInt, bInt); } /// /// Includes a and b. /// /// /// /// public static int IncludeFlags(int a, int b) { return a | b; } /// /// Gets the overlapping values of the given enum flags. /// /// /// /// public static T GetFlagsIntersection(params T[] values) where T : struct, IConvertible { if (values == null) throw new ArgumentNullException("values"); if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); if (values.Length == 0) return default(T); int output = 0; bool first = true; foreach (T value in values) { if (first) { output = (int)(object)value; first = false; } else { output &= (int)(object)value; } if (output == 0) return default(T); } return (T)(object)output; } /// /// Gets the overlapping values of the given enum flags. /// /// /// /// /// public static T GetFlagsIntersection(T a, T b) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); int aInt = (int)(object)a; int bInt = (int)(object)b; return (T)(object)(aInt & bInt); } /// /// Gets all of the set flags on the given enum. /// /// /// /// public static IEnumerable GetFlags(T value) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); Type type = typeof(T); int valueInt = (int)(object)value; Dictionary cache; if (!s_EnumFlagsCache.TryGetValue(type, out cache)) { cache = new Dictionary(); s_EnumFlagsCache[type] = cache; } object flags; if (!cache.TryGetValue(valueInt, out flags)) { flags = GetValues().Where(e => HasFlag(value, e)).ToArray(); cache[valueInt] = flags; } return flags as T[]; } /// /// Gets all of the set flags on the given enum type except 0. /// /// /// public static IEnumerable GetFlagsExceptNone() where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); T allValue = GetFlagsAllValue(); return GetFlagsExceptNone(allValue); } /// /// Gets all of the set flags on the given enum except 0. /// /// /// /// public static IEnumerable GetFlagsExceptNone(T value) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); return GetFlags(value).Except(default(T)); } /// /// Gets all of the flag combinations on the given flag enum value. /// /// IE: If you have an enum type with flags{a, b, c}, and you pass this method {a|b}, /// It will return {a, b, a|b} /// /// /// /// public static IEnumerable GetAllFlagCombinationsExceptNone(T value) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); int maxEnumValue = (GetValues().Max(v => (int)(object)v) * 2) -1; return Enumerable.Range(1, maxEnumValue).Select(i => (T)(object)i ).Where(v => HasFlags(value, v)); } /// /// Gets an enum value of the given type with every flag set. /// /// /// public static T GetFlagsAllValue() where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); int output = GetValues().Aggregate(0, (current, value) => current | (int)(object)value); return (T)Enum.ToObject(typeof(T), output); } /// /// Gets an enum value of the given type with every flag set. /// /// /// public static int GetFlagsAllValue(Type type) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); return GetValuesUncached(type).Aggregate(0, (current, value) => current | value); } /// /// Returns true if the enum contains the given flag. /// /// /// /// /// public static bool HasFlag(T value, T flag) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); if (!IsEnum(flag)) throw new ArgumentException(string.Format("{0} is not an enum", flag.GetType().Name), "flag"); return HasFlags(value, flag); } /// /// Returns true if the enum contains the given flag. /// /// /// /// public static bool HasFlag(int value, int flag) { return HasFlags(value, flag); } /// /// Returns true if the enum contains all of the given flags. /// /// /// /// /// public static bool HasFlags(T value, T flags) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); if (!IsEnum(flags)) throw new ArgumentException(string.Format("{0} is not an enum", flags.GetType().Name), "flags"); int a = (int)(object)value; int b = (int)(object)flags; return HasFlags(a, b); } /// /// Returns true if the enum contains the given flag. /// /// /// /// public static bool HasFlags(int value, int flags) { return (value & flags) == flags; } /// /// Returns true if only a single flag is set on the given enum value. /// /// /// /// public static bool HasSingleFlag(T value) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); int numeric = (int)(object)value; return HasAnyFlags(numeric) && !HasMultipleFlags(numeric); } /// /// Returns true if the enum has more than 1 flag set. /// /// /// public static bool HasMultipleFlags(T value) where T : struct, IConvertible { if (!IsEnum(value)) throw new ArgumentException(string.Format("{0} is not an enum", value.GetType().Name), "value"); return HasMultipleFlags((int)(object)value); } /// /// Returns true if the enum has more than 1 flag set. /// /// /// [PublicAPI] public static bool HasMultipleFlags(int value) { return (value & (value - 1)) != 0; } /// /// Returns true if the enum contains any flags. /// /// /// [PublicAPI] public static bool HasAnyFlags(T value) where T : struct, IConvertible { return HasAnyFlags((int)(object)value); } /// /// Returns true if the enum has any flags set. /// /// /// [PublicAPI] public static bool HasAnyFlags(int value) { return value > 0; } /// /// Returns true if the enum contains any of the given flag values. /// /// /// /// [PublicAPI] public static bool HasAnyFlags(T value, T other) where T : struct, IConvertible { T intersection = GetFlagsIntersection(value, other); return HasAnyFlags(intersection); } #endregion #region Conversion /// /// Shorthand for parsing string to enum. /// /// /// /// /// public static T Parse(string data, bool ignoreCase) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); T output; if (TryParse(data, ignoreCase, out output)) return output; string message = string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), typeof(T).Name); throw new FormatException(message); } /// /// Shorthand for parsing string to enum. /// /// /// /// /// public static int Parse(Type type, string data, bool ignoreCase) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); int output; if (TryParse(type, data, ignoreCase, out output)) return output; string message = string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), type.Name); throw new FormatException(message); } /// /// Shorthand for parsing a string to enum. Returns false if the parse failed. /// /// /// /// /// /// public static bool TryParse(string data, bool ignoreCase, out T result) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); result = default(T); try { result = (T)Enum.Parse(typeof(T), data, ignoreCase); return true; } catch (Exception) { return false; } } /// /// Shorthand for parsing a string to enum. Returns false if the parse failed. /// /// /// /// /// /// public static bool TryParse(Type type, string data, bool ignoreCase, out int result) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); result = 0; try { result = (int)Enum.Parse(type, data, ignoreCase); return true; } catch (Exception) { return false; } } /// /// Shorthand for parsing string to enum. /// Will fail if the resulting value is not defined as part of the enum. /// /// /// /// /// public static T ParseStrict(string data, bool ignoreCase) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); T output; try { output = Parse(data, ignoreCase); } catch (Exception e) { throw new FormatException( string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), typeof(T).Name), e); } if (!IsDefined(output)) throw new ArgumentOutOfRangeException(string.Format("{0} is not a valid {1}", output, typeof(T).Name)); return output; } /// /// Shorthand for parsing string to enum. /// Will fail if the resulting value is not defined as part of the enum. /// /// /// /// /// public static int ParseStrict(Type type, string data, bool ignoreCase) { if (type == null) throw new ArgumentNullException("type"); if (!IsEnumType(type)) throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); int output; try { output = Parse(type, data, ignoreCase); } catch (Exception e) { throw new FormatException( string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), type.Name), e); } if (!IsDefined(type, output)) throw new ArgumentOutOfRangeException(string.Format("{0} is not a valid {1}", output, type.Name)); return output; } /// /// Shorthand for parsing a string to enum. Returns false if the parse failed. /// Will fail if the resulting value is not defined as part of the enum. /// /// /// /// /// /// public static bool TryParseStrict(string data, bool ignoreCase, out T result) where T : struct, IConvertible { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); result = default(T); try { result = ParseStrict(data, ignoreCase); return true; } catch (Exception) { return false; } } #endregion } }