using System; using System.Collections.Generic; using System.Linq; using ICD.Common.Properties; using ICD.Common.Utils.Collections; using ICD.Common.Utils.Extensions; #if SIMPLSHARP using System.Globalization; using Crestron.SimplSharp.Reflection; #else using System.Reflection; #endif namespace ICD.Common.Utils { public static class EnumUtils { private static readonly Dictionary> s_EnumValuesCache = new Dictionary>(); /// /// Returns true if the given type is an enum. /// /// public static bool IsEnumType(Type type) { if (type == null) throw new ArgumentNullException("type"); return type.IsAssignableTo(typeof(Enum)) || type #if !SIMPLSHARP .GetTypeInfo() #endif .IsEnum; } /// /// Returns true if the given type is an enum. /// /// public static bool IsEnumType() { return IsEnumType(typeof(T)); } /// /// Returns true if the given value is an enum. /// /// public static bool IsEnum(object value) { return value != null && IsEnumType(value.GetType()); } /// /// Returns true if the given value is defined as part of the given enum type. /// /// /// /// public static bool IsDefined(T value) { if (!IsEnumType(typeof(T))) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); if (!IsFlagsEnum()) return GetValues().Any(v => v.Equals(value)); int valueInt = (int)GetUnderlyingValue(value); // Check if all of the flag values are defined foreach (T flag in GetFlags(value)) { int flagInt = (int)GetUnderlyingValue(flag); valueInt = valueInt - flagInt; } return valueInt == 0; } #region Values /// /// Gets the underlying value of the enum. /// /// /// public static object GetUnderlyingValue(T value) { if (!IsEnumType(typeof(T))) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); #if SIMPLSHARP return Convert.ChangeType(value, ToEnum(value).GetTypeCode(), CultureInfo.InvariantCulture); #else return Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType())); #endif } /// /// Gets the values from an enumeration. /// /// /// public static IEnumerable GetValues() { return GetValues(typeof(T)).Cast(); } /// /// Gets the values from an enumeration. /// /// /// public static IEnumerable GetValues(Type type) { if (type == null) throw new ArgumentNullException("type"); // Reflection is slow and this method is called a lot, so we cache the results. if (!s_EnumValuesCache.ContainsKey(type)) s_EnumValuesCache[type] = GetValuesUncached(type).ToHashSet(); return s_EnumValuesCache[type]; } /// /// 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)); } /// /// Gets the 0 value for the given enum type. /// /// /// public static T GetNoneValue() { if (!IsEnumType(typeof(T))) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); return (T)(object)0; } /// /// Gets the values from an enumeration except the 0 value. /// /// /// public static IEnumerable GetValuesExceptNone() { if (!IsEnumType(typeof(T))) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); return GetValuesExceptNone(typeof(T)).Cast(); } /// /// Gets the values from an enumeration except the 0 value. /// /// /// public static IEnumerable GetValuesExceptNone(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 GetValues(type).Where(v => (int)v != 0); } #endregion #region Flags /// /// Returns true if the given enum type has the Flags attribute set. /// /// /// public static bool IsFlagsEnum() { if (!IsEnumType()) throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); 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"); if (!IsEnumType(type)) throw new InvalidOperationException(string.Format("{0} is not an enum", type.Name)); return type #if !SIMPLSHARP .GetTypeInfo() #endif .IsDefined(typeof(FlagsAttribute), false); } /// /// Gets the overlapping values of the given enum flags. /// /// /// /// public static T GetFlagsIntersection(params T[] values) { if (values.Length == 0) return GetNoneValue(); int output = (int)(object)values.First(); foreach (T item in values.Skip(1)) output &= (int)(object)item; return (T)Enum.ToObject(typeof(T), output); } /// /// Gets all of the set flags on the given enum. /// /// /// /// public static IEnumerable GetFlags(T value) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); return GetValues().Where(e => HasFlag(value, e)); } /// /// Gets all of the set flags on the given enum except 0. /// /// /// /// public static IEnumerable GetFlagsExceptNone(T value) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); T none = GetNoneValue(); return GetFlags(value).Except(none); } /// /// Gets an enum value of the given type with every flag set. /// /// /// public static T GetFlagsAllValue() { 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); } /// /// Returns true if the enum contains the given flag. /// /// /// /// /// public static bool HasFlag(T value, T flag) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); if (!IsEnum(flag)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", flag == null ? "NULL" : flag.GetType().Name), "flag"); return ToEnum(value).HasFlag(ToEnum(flag)); } /// /// Returns true if the enum contains all of the given flags. /// /// /// /// /// public static bool HasFlags(T value, T flags) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); if (!IsEnum(flags)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", flags == null ? "NULL" : flags.GetType().Name), "flags"); return ToEnum(value).HasFlags(ToEnum(flags)); } /// /// Returns true if only a single flag is set on the given enum value. /// /// /// /// public static bool HasSingleFlag(T value) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); return (int)(object)value != (int)(object)GetNoneValue() && !HasMultipleFlags(value); } /// /// Returns true if the enum has more than 1 flag set. /// /// /// public static bool HasMultipleFlags(T value) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : 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) { return GetFlagsExceptNone(value).Any(); } /// /// Returns true if the enum contains any of the given flag values. /// /// /// /// [PublicAPI] public static bool HasAnyFlags(T value, T other) { 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) { 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 a string to enum. Returns false if the parse failed. /// /// /// /// /// /// public static bool TryParse(string data, bool ignoreCase, out T result) { 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 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) { 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 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) { 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; } } /// /// Converts the given enum value to an Enum. /// /// /// /// public static Enum ToEnum(T value) { if (!IsEnum(value)) // ReSharper disable once CompareNonConstrainedGenericWithNull throw new ArgumentException(string.Format("{0} is not an enum", value == null ? "NULL" : value.GetType().Name), "value"); return ToEnum((object)value); } /// /// Converts the given enum value to an Enum. /// /// /// public static Enum ToEnum(object value) { return (Enum)value; } #endregion } }