commit 52948a6e7e93957f1d51ba1492215de33c4b0588 Author: jeff.thompson Date: Wed Jun 21 22:41:54 2017 -0400 Created S# .sln and moved project to src folder diff --git a/ICD.Common/Attributes/AbstractIcdAttribute.cs b/ICD.Common/Attributes/AbstractIcdAttribute.cs new file mode 100644 index 0000000..6f08333 --- /dev/null +++ b/ICD.Common/Attributes/AbstractIcdAttribute.cs @@ -0,0 +1,40 @@ +using System; + +namespace ICD.Common.Attributes +{ + /// + /// AbstractIcdAttribute is the base class for all ICD attributes. + /// It provides a global cache for looking up symbols via an attribute type. + /// + public abstract class AbstractIcdAttribute : Attribute + { + private readonly int m_HashCode; + + #region Constructors + + /// + /// Constructor. + /// + protected AbstractIcdAttribute() + { + // Duplicate attributes (E.g. [A, A]) are considered to be the same instance by reflection. + // We get around this by using a GUID for the hash code. + m_HashCode = Guid.NewGuid().GetHashCode(); + } + + #endregion + + #region Methods + + /// + /// Gets the hash code for the instance. + /// + /// + public override int GetHashCode() + { + return m_HashCode; + } + + #endregion + } +} diff --git a/ICD.Common/Attributes/KrangPluginAttribute.cs b/ICD.Common/Attributes/KrangPluginAttribute.cs new file mode 100644 index 0000000..4ad5161 --- /dev/null +++ b/ICD.Common/Attributes/KrangPluginAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace ICD.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Assembly)] + public sealed class KrangPluginAttribute : Attribute + { + } +} diff --git a/ICD.Common/Attributes/Properties/SettingsProperty.cs b/ICD.Common/Attributes/Properties/SettingsProperty.cs new file mode 100644 index 0000000..6ae1c25 --- /dev/null +++ b/ICD.Common/Attributes/Properties/SettingsProperty.cs @@ -0,0 +1,38 @@ +using System; +using ICD.Common.Properties; + +namespace ICD.Common.Attributes.Properties +{ + /// + /// Provides information on a settings property. + /// + [PublicAPI] + public sealed class SettingsProperty : Attribute + { + public enum ePropertyType + { + [PublicAPI] Default, + [PublicAPI] PortId, + [PublicAPI] DeviceId, + [PublicAPI] Ipid, + [PublicAPI] Enum + } + + private readonly ePropertyType m_PropertyType; + + /// + /// Gets the property type. + /// + [PublicAPI] + public ePropertyType PropertyType { get { return m_PropertyType; } } + + /// + /// Constructor. + /// + /// + public SettingsProperty(ePropertyType propertyType) + { + m_PropertyType = propertyType; + } + } +} diff --git a/ICD.Common/Attributes/Rpc/RpcAttribute.cs b/ICD.Common/Attributes/Rpc/RpcAttribute.cs new file mode 100644 index 0000000..c3ce074 --- /dev/null +++ b/ICD.Common/Attributes/Rpc/RpcAttribute.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if SIMPLSHARP +using Crestron.SimplSharp.Reflection; +#else +using System.Reflection; +#endif +using ICD.Common.Properties; +using ICD.Common.Utils; +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Attributes.Rpc +{ + /// + /// Represents a method that can be called by the server via RPC. + /// + [PublicAPI] + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] + public sealed class RpcAttribute : AbstractIcdAttribute + { + private static readonly Dictionary>> s_MethodCache; + private static readonly Dictionary>> s_PropertyCache; + + private static readonly SafeCriticalSection s_MethodCacheSection; + private static readonly SafeCriticalSection s_PropertyCacheSection; + + private readonly string m_Key; + + #region Constructors + + /// + /// Constructor. + /// + static RpcAttribute() + { + s_MethodCache = new Dictionary>>(); + s_PropertyCache = new Dictionary>>(); + + s_MethodCacheSection = new SafeCriticalSection(); + s_PropertyCacheSection = new SafeCriticalSection(); + } + + /// + /// Constructor. + /// + /// + public RpcAttribute(string key) + { + m_Key = key; + } + + #endregion + + #region Methods + + /// + /// Gets the method on the client with the given key, matching the parameter types. + /// + /// + /// + /// + /// + [CanBeNull] + public static MethodInfo GetMethod(object client, string key, IEnumerable parameters) + { + return GetMethods(client, key).FirstOrDefault(m => ReflectionUtils.MatchesMethodParameters(m, parameters)); + } + + /// + /// Gets the property on the client with the given key, matching the parameter type. + /// + /// + /// + /// + /// + [CanBeNull] + public static PropertyInfo GetProperty(object client, string key, object parameter) + { + return GetProperties(client, key).FirstOrDefault(p => ReflectionUtils.MatchesPropertyParameter(p, parameter)); + } + + #endregion + + #region Private Methods + + /// + /// Returns the methods on the client with the given key. + /// + /// + /// + /// + private static IEnumerable GetMethods(object client, string key) + { + s_MethodCacheSection.Enter(); + + try + { + Type clientType = client.GetType(); + + // Cache the methods for the key so subsequent calls are faster. + if (!s_MethodCache.ContainsKey(clientType)) + s_MethodCache[clientType] = new Dictionary>(); + + if (!s_MethodCache[clientType].ContainsKey(key)) + { + s_MethodCache[clientType][key] = + clientType +#if SIMPLSHARP + .GetCType() +#else + .GetTypeInfo() +#endif + .GetMethods(BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance) + .Where(m => m.GetCustomAttributes(true) + .Any(a => a.m_Key == key)) + .ToHashSet(); + } + + return s_MethodCache[clientType][key]; + } + finally + { + s_MethodCacheSection.Leave(); + } + } + + /// + /// Returns the properties on the client with the given key. + /// + /// + /// + /// + private static IEnumerable GetProperties(object client, string key) + { + s_PropertyCacheSection.Enter(); + + try + { + Type clientType = client.GetType(); + + // Cache the properties for the key so subsequent calls are faster. + if (!s_PropertyCache.ContainsKey(clientType)) + s_PropertyCache[clientType] = new Dictionary>(); + + if (!s_PropertyCache[clientType].ContainsKey(key)) + { + s_PropertyCache[clientType][key] = + clientType +#if SIMPLSHARP + .GetCType() +#else + .GetTypeInfo() +#endif + .GetProperties(BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance) + .Where(p => p.CanWrite && p.GetCustomAttributes(true) + .Any(a => a.m_Key == key)) + .ToHashSet(); + } + + return s_PropertyCache[clientType][key]; + } + finally + { + s_PropertyCacheSection.Leave(); + } + } + + #endregion + } +} diff --git a/ICD.Common/EventArguments/BoolEventArgs.cs b/ICD.Common/EventArguments/BoolEventArgs.cs new file mode 100644 index 0000000..8ce49a5 --- /dev/null +++ b/ICD.Common/EventArguments/BoolEventArgs.cs @@ -0,0 +1,13 @@ +namespace ICD.Common.EventArguments +{ + public sealed class BoolEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public BoolEventArgs(bool data) : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/CharEventArgs.cs b/ICD.Common/EventArguments/CharEventArgs.cs new file mode 100644 index 0000000..91b54b9 --- /dev/null +++ b/ICD.Common/EventArguments/CharEventArgs.cs @@ -0,0 +1,12 @@ +using ICD.Common.Properties; + +namespace ICD.Common.EventArguments +{ + [PublicAPI] + public sealed class CharEventArgs : GenericEventArgs + { + public CharEventArgs(char value) : base(value) + { + } + } +} diff --git a/ICD.Common/EventArguments/DateTimeEventArgs.cs b/ICD.Common/EventArguments/DateTimeEventArgs.cs new file mode 100644 index 0000000..d91577c --- /dev/null +++ b/ICD.Common/EventArguments/DateTimeEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace ICD.Common.EventArguments +{ + public sealed class DateTimeEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public DateTimeEventArgs(DateTime data) + : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/FloatEventArgs.cs b/ICD.Common/EventArguments/FloatEventArgs.cs new file mode 100644 index 0000000..0048d70 --- /dev/null +++ b/ICD.Common/EventArguments/FloatEventArgs.cs @@ -0,0 +1,14 @@ +namespace ICD.Common.EventArguments +{ + public sealed class FloatEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public FloatEventArgs(float data) + : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/GenericEventArgs.cs b/ICD.Common/EventArguments/GenericEventArgs.cs new file mode 100644 index 0000000..442ee03 --- /dev/null +++ b/ICD.Common/EventArguments/GenericEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace ICD.Common.EventArguments +{ + public abstract class GenericEventArgs : EventArgs + { + public T Data { get; private set; } + + /// + /// Constructor. + /// + /// + protected GenericEventArgs(T data) + { + Data = data; + } + } +} diff --git a/ICD.Common/EventArguments/IntEventArgs.cs b/ICD.Common/EventArguments/IntEventArgs.cs new file mode 100644 index 0000000..29d80ac --- /dev/null +++ b/ICD.Common/EventArguments/IntEventArgs.cs @@ -0,0 +1,13 @@ +namespace ICD.Common.EventArguments +{ + public sealed class IntEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public IntEventArgs(int data) : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/StringEventArgs.cs b/ICD.Common/EventArguments/StringEventArgs.cs new file mode 100644 index 0000000..a126eac --- /dev/null +++ b/ICD.Common/EventArguments/StringEventArgs.cs @@ -0,0 +1,13 @@ +namespace ICD.Common.EventArguments +{ + public sealed class StringEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public StringEventArgs(string data) : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/TcpReceiveEventArgs.cs b/ICD.Common/EventArguments/TcpReceiveEventArgs.cs new file mode 100644 index 0000000..2d0a0f2 --- /dev/null +++ b/ICD.Common/EventArguments/TcpReceiveEventArgs.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using ICD.Common.Utils; + +namespace ICD.Common.EventArguments +{ + public sealed class TcpReceiveEventArgs : EventArgs + { + private readonly uint m_ClientId; + private readonly string m_Data; + + public uint ClientId { get { return m_ClientId; } } + + public string Data { get { return m_Data; } } + + /// + /// Constructor. + /// + /// + /// + public TcpReceiveEventArgs(uint clientId, IEnumerable data) + { + m_ClientId = clientId; + m_Data = StringUtils.ToString(data); + } + + /// + /// Constructor. + /// + /// + /// + /// + public TcpReceiveEventArgs(uint clientId, IEnumerable data, int length) + { + m_ClientId = clientId; + m_Data = StringUtils.ToString(data, length); + } + } +} diff --git a/ICD.Common/EventArguments/UShortEventArgs.cs b/ICD.Common/EventArguments/UShortEventArgs.cs new file mode 100644 index 0000000..91df33b --- /dev/null +++ b/ICD.Common/EventArguments/UShortEventArgs.cs @@ -0,0 +1,16 @@ +using ICD.Common.Properties; + +namespace ICD.Common.EventArguments +{ + [PublicAPI] + public sealed class UShortEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public UShortEventArgs(ushort data) : base(data) + { + } + } +} diff --git a/ICD.Common/EventArguments/XmlRecursionEventArgs.cs b/ICD.Common/EventArguments/XmlRecursionEventArgs.cs new file mode 100644 index 0000000..a421eb6 --- /dev/null +++ b/ICD.Common/EventArguments/XmlRecursionEventArgs.cs @@ -0,0 +1,29 @@ +using System; +using ICD.Common.Properties; + +namespace ICD.Common.EventArguments +{ + public sealed class XmlRecursionEventArgs : EventArgs + { + [PublicAPI] + public string Outer { get; private set; } + + [PublicAPI] + public string[] Path { get; private set; } + + #region Constructors + + /// + /// Constructor. + /// + /// + /// + public XmlRecursionEventArgs(string outer, string[] path) + { + Outer = outer; + Path = path; + } + + #endregion + } +} diff --git a/ICD.Common/ICD.Common_NetStandard.csproj b/ICD.Common/ICD.Common_NetStandard.csproj new file mode 100644 index 0000000..c4e528f --- /dev/null +++ b/ICD.Common/ICD.Common_NetStandard.csproj @@ -0,0 +1,35 @@ + + + + Library + netstandard1.6 + + + + TRACE;DEBUG;STANDARD + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ICD.Common/ICD.Common_SimplSharp.csproj b/ICD.Common/ICD.Common_SimplSharp.csproj new file mode 100644 index 0000000..cec95b6 --- /dev/null +++ b/ICD.Common/ICD.Common_SimplSharp.csproj @@ -0,0 +1,167 @@ + + + Release + AnyCPU + 9.0.30729 + 2.0 + {CB4055C6-D59E-479E-9C56-2C92335A7D9A} + Library + Properties + ICD.Common + ICD.Common + {0B4745B0-194B-4BB6-8E21-E9057CA92500};{4D628B5B-2FBC-4AA6-8C16-197242AEB884};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + WindowsCE + E2BECB1F-8C8C-41ba-B736-9BE7D946A398 + 5.0 + SmartDeviceProject1 + v3.5 + Windows CE + + + + + .allowedReferenceRelatedFileExtensions + true + full + false + bin\Debug\ + DEBUG;TRACE;SIMPLSHARP + prompt + 4 + 512 + true + true + off + + + .allowedReferenceRelatedFileExtensions + none + true + bin\Release\ + prompt + 4 + 512 + true + true + off + SIMPLSHARP + + + + + False + C:\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll + + + False + C:\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll + + + False + C:\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll + + + False + C:\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if /I "$(ConfigurationName)" == "Release" Eazfuscator.NET.exe "$(TargetPath)" --msbuild-project-path "$(ProjectPath)" --msbuild-project-configuration "$(ConfigurationName)" --msbuild-project-platform "$(PlatformName)" --msbuild-solution-path "$(SolutionPath)" -n --newline-flush -v 5.2 --configuration-file="$(ProjectDir)ObfuscationSettings.cs" +rem S# Pro preparation will execute after these operations + + \ No newline at end of file diff --git a/ICD.Common/ObfuscationSettings.cs b/ICD.Common/ObfuscationSettings.cs new file mode 100644 index 0000000..a05d68f --- /dev/null +++ b/ICD.Common/ObfuscationSettings.cs @@ -0,0 +1,6 @@ +using System.Reflection; + +[assembly: Obfuscation(Feature = "platform api: System.Threading.Thread, System.Reflection.*, System.IO.Stream, System.Windows.Forms.*", Exclude = true)] +[assembly: Obfuscation(Feature = "rename symbol names with printable characters", Exclude = false)] +[assembly: Obfuscation(Feature = "code control flow obfuscation", Exclude = false)] +[assembly: Obfuscation(Feature = "Apply to type * when class: renaming", Exclude = true, ApplyToMembers = false)] diff --git a/ICD.Common/Properties/Annotations.cs b/ICD.Common/Properties/Annotations.cs new file mode 100644 index 0000000..99b4e6f --- /dev/null +++ b/ICD.Common/Properties/Annotations.cs @@ -0,0 +1,713 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedParameter.Local +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming +// ReSharper disable CheckNamespace + +namespace ICD.Common.Properties +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage + /// + /// + /// [CanBeNull] public object Test() { return null; } + /// public void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of the marked element could never be null + /// + /// + /// [NotNull] public object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class NotNullAttribute : Attribute + { + } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form + /// + /// + /// [StringFormatMethod("message")] + /// public void ShowError(string message, params object[] args) { /* do something */ } + /// public void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method, + AllowMultiple = false, Inherited = true)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of + /// + /// + /// public void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Indicates that the method is contained in a type that implements + /// interface + /// and this method is used to notify that some property value changed + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// private string _name; + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() + { + } + + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) + { + } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// public class Foo { + /// private string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) + { + } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// class UsesNoEquality { + /// public void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Interface | AttributeTargets.Class | + AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// public class ComponentAttribute : Attribute { } + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// public class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly + /// (e.g. via reflection, in external library), so this symbol + /// will not be marked as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Itself) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Itself) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper + /// to not mark symbols marked with such attributes as unused + /// (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Itself) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Itself) + { + } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public MeansImplicitUseAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + + /// Only entity marked with attribute considered used + Access = 1, + + /// Indicates implicit assignment to a member + Assign = 2, + + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + + /// Indicates implicit instantiation of a type + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly + /// when marked with + /// or + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Itself = 1, + + /// Members of entity marked with attribute are considered used + Members = 2, + + /// Entity marked with attribute and all its members considered used + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used + /// + [MeansImplicitUse] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [NotNull] + public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled + /// when the invoked method is on stack. If the parameter is a delegate, + /// indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated + /// while the method is executed + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] + public sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute + /// + /// + /// [Pure] private int Multiply(int x, int y) { return x * y; } + /// public void Foo() { + /// const int a = 2, b = 2; + /// Multiply(a, b); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public sealed class PureAttribute : Attribute + { + } + + /// + /// Indicates that a parameter is a path to a file or a folder + /// within a web project. Path can be relative or absolute, + /// starting from web root (~) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() + { + } + + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + [NotNull] + public string BasePath { get; private set; } + } + + // ASP.NET MVC attributes + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute(string format) + { + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute(string format) + { + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute(string format) + { + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute(string format) + { + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute(string format) + { + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute(string format) + { + } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() + { + } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : PathReferenceAttribute + { + public AspMvcAreaAttribute() + { + } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC controller. If applied to a method, + /// the MVC controller name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() + { + } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, Object) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC partial view. If applied to a method, + /// the MVC partial view name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute + { + } + + /// + /// ASP.NET MVC attribute. Allows disabling all inspections + /// for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSupressViewErrorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : PathReferenceAttribute + { + } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute + { + } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Field, Inherited = true)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() + { + } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | + AttributeTargets.Property, Inherited = true)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + // Razor attributes + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] + public sealed class RazorSectionAttribute : Attribute + { + } +} diff --git a/ICD.Common/Properties/AssemblyInfo.cs b/ICD.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..866ba39 --- /dev/null +++ b/ICD.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; + +[assembly: AssemblyTitle("ICD.SimplSharp.Common")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ICD.SimplSharp.Common")] +[assembly: AssemblyCopyright("Copyright © ICD Systems 2017")] +[assembly: AssemblyVersion("1.0.1.*")] diff --git a/ICD.Common/Properties/ControlSystem.cfg b/ICD.Common/Properties/ControlSystem.cfg new file mode 100644 index 0000000..e69de29 diff --git a/ICD.Common/Services/Logging/ILoggerService.cs b/ICD.Common/Services/Logging/ILoggerService.cs new file mode 100644 index 0000000..db37aa7 --- /dev/null +++ b/ICD.Common/Services/Logging/ILoggerService.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using ICD.Common.Properties; +using ICD.Common.Utils; + +namespace ICD.Common.Services.Logging +{ + public enum eSeverity + { + Emergency = 0, + Alert = 1, + Critical = 2, + Error = 3, + Warning = 4, + Notice = 5, + Informational = 6, + Debug = 7 + } + + public interface ILoggerService + { + [PublicAPI] + event EventHandler OnEntryAdded; + + [PublicAPI] + event EventHandler OnSeverityLevelChanged; + + /// + /// Gets and sets the severity level. + /// + [PublicAPI] + eSeverity SeverityLevel { get; set; } + + /// + /// Adds the log item. + /// + /// Log entry to add + [PublicAPI] + void AddEntry(LogItem item); + + /// + /// Gets the log history. + /// + /// + [PublicAPI] + KeyValuePair[] GetHistory(); + } + + /// + /// Extension methods for ILoggerService. + /// + public static class LoggerServiceExtensions + { + /// + /// Adds the log item with string formatting. + /// + /// + /// Severity Code, 0 - 7 + /// Message Text format string + [PublicAPI] + public static void AddEntry(this ILoggerService extends, eSeverity severity, string message) + { + LogItem item = new LogItem(severity, message); + extends.AddEntry(item); + } + + /// + /// Adds the log item with string formatting. + /// + /// + /// Severity Code, 0 - 7 + /// Message Text format string + /// objects to format into the string + [PublicAPI] + public static void AddEntry(this ILoggerService extends, eSeverity severity, string message, params object[] args) + { + extends.AddEntry(severity, string.Format(message, args)); + } + + [PublicAPI] + public static void AddEntry(this ILoggerService extends, eSeverity severity, Exception e, string message) + { + extends.AddEntry(severity, string.Format("{0}: {1}{2}{3}{2}{4}", e.GetType().Name, message, + IcdEnvironment.NewLine, e.Message, e.StackTrace)); + } + + [PublicAPI] + public static void AddEntry(this ILoggerService extends, eSeverity severity, Exception e, string message, + params object[] args) + { + extends.AddEntry(severity, e, string.Format(message, args)); + } + } +} diff --git a/ICD.Common/Services/Logging/LogItem.cs b/ICD.Common/Services/Logging/LogItem.cs new file mode 100644 index 0000000..ca4fafa --- /dev/null +++ b/ICD.Common/Services/Logging/LogItem.cs @@ -0,0 +1,126 @@ +using System; +using System.Text; +using ICD.Common.Properties; +using ICD.Common.Utils; + +namespace ICD.Common.Services.Logging +{ + /// + /// Log Entry Item + /// + public struct LogItem + { + private readonly string m_Message; + private readonly eSeverity m_Severity; + private readonly DateTime m_Timestamp; + + #region Properties + + /// + /// Accessor only for timestamp. + /// + [PublicAPI] + public DateTime Timestamp { get { return m_Timestamp; } } + + /// + /// Get/Set for severity level. + /// + public eSeverity Severity { get { return m_Severity; } } + + /// + /// Get/Set for message string. + /// + public string Message { get { return m_Message; } } + + #endregion + + #region Constructors + + /// + /// Creates a new LogItem object with the specified values. + /// + /// Severity Level, between 0 and 7 + /// Error message text + public LogItem(eSeverity severity, string message) + { + m_Severity = severity; + m_Message = message; + m_Timestamp = IcdEnvironment.GetLocalTime(); + } + + #endregion + + #region Methods + + /// + /// Return the text format to send to Fusion + /// + /// text format for fusion, including timestamp, severity, and message + [PublicAPI] + public string GetFusionLogText() + { + StringBuilder s = new StringBuilder(); + + s.Append(Timestamp.ToString("yyyyMMddHHmmss")); + s.Append("||"); + s.Append((int)Severity); + s.Append("||"); + s.Append(Message); + + return s.ToString(); + } + + /// + /// Implementing default equality. + /// + /// + /// + /// + public static bool operator ==(LogItem a1, LogItem a2) + { + return a1.Equals(a2); + } + + /// + /// Implementing default inequality. + /// + /// + /// + /// + public static bool operator !=(LogItem a1, LogItem a2) + { + return !(a1 == a2); + } + + /// + /// Returns true if this instance is equal to the given object. + /// + /// + /// + public override bool Equals(object other) + { + if (other == null || GetType() != other.GetType()) + return false; + + return GetHashCode() == ((LogItem)other).GetHashCode(); + } + + /// + /// Gets the hashcode for this instance. + /// + /// + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (m_Message == null ? 0 : m_Message.GetHashCode()); + hash = hash * 23 + (int)m_Timestamp.Ticks; + hash = hash * 23 + (int)m_Severity; + return hash; + } + } + + #endregion + } +} diff --git a/ICD.Common/Services/Logging/LogItemEventArgs.cs b/ICD.Common/Services/Logging/LogItemEventArgs.cs new file mode 100644 index 0000000..d55adf0 --- /dev/null +++ b/ICD.Common/Services/Logging/LogItemEventArgs.cs @@ -0,0 +1,16 @@ +using ICD.Common.EventArguments; + +namespace ICD.Common.Services.Logging +{ + public sealed class LogItemEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public LogItemEventArgs(LogItem item) + : base(item) + { + } + } +} diff --git a/ICD.Common/Services/Logging/SeverityEventArgs.cs b/ICD.Common/Services/Logging/SeverityEventArgs.cs new file mode 100644 index 0000000..23b0d82 --- /dev/null +++ b/ICD.Common/Services/Logging/SeverityEventArgs.cs @@ -0,0 +1,16 @@ +using ICD.Common.EventArguments; + +namespace ICD.Common.Services.Logging +{ + public sealed class SeverityEventArgs : GenericEventArgs + { + /// + /// Constructor. + /// + /// + public SeverityEventArgs(eSeverity data) + : base(data) + { + } + } +} diff --git a/ICD.Common/Services/ServiceNotFoundException.cs b/ICD.Common/Services/ServiceNotFoundException.cs new file mode 100644 index 0000000..18d8b70 --- /dev/null +++ b/ICD.Common/Services/ServiceNotFoundException.cs @@ -0,0 +1,28 @@ +using System; + +namespace ICD.Common.Services +{ + public sealed class ServiceNotFoundException : Exception + { + private const string DEFAULT_MESSAGE = "The requested service {0} was not found in the service provider"; + + public Type ServiceType { get; set; } + + public ServiceNotFoundException() + { + } + + public ServiceNotFoundException(string message) : base(message) + { + } + + public ServiceNotFoundException(string message, Exception inner) : base(message, inner) + { + } + + public ServiceNotFoundException(Type type) : base(string.Format(DEFAULT_MESSAGE, type.Name)) + { + ServiceType = type; + } + } +} diff --git a/ICD.Common/Services/ServiceProvider.cs b/ICD.Common/Services/ServiceProvider.cs new file mode 100644 index 0000000..5eac93c --- /dev/null +++ b/ICD.Common/Services/ServiceProvider.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; +using ICD.Common.Utils; + +namespace ICD.Common.Services +{ + public sealed class ServiceProvider : IDisposable + { + #region Static + + private static ServiceProvider s_Instance; + + private static ServiceProvider Instance { get { return s_Instance ?? (s_Instance = new ServiceProvider()); } } + + /// + /// Retrieves the registered service of the given type. Use this for required dependencies. + /// + /// service type to retrieve + /// + /// Thrown if a service of the given type is not registered + [PublicAPI] + [NotNull] + public static TService GetService() + { + return (TService)GetService(typeof(TService)); + } + + /// + /// Retrieves the registered service of the given type. Use this for required dependencies. + /// + /// service type to retrieve + /// + /// Thrown if a service of the given type is not registered + [PublicAPI] + [NotNull] + public static object GetService(Type tService) + { + return Instance.GetServiceInstance(tService); + } + + /// + /// Retrieves the registered service of the given type. Returns null if the service type was not found. + /// Use this for optional dependencies. + /// + /// service type to retrieve + /// requested service or null if that service type is not registered + [PublicAPI] + [CanBeNull] + public static TService TryGetService() + { + return (TService)TryGetService(typeof(TService)); + } + + /// + /// Retrieves the registered service of the given type. Returns null if the service type was not found. + /// Use this for optional dependencies. + /// + /// service type to retrieve + /// requested service or null if that service type is not registered + [PublicAPI] + [CanBeNull] + public static object TryGetService(Type tService) + { + try + { + return Instance.GetServiceInstance(tService); + } + catch (ServiceNotFoundException) + { + return null; + } + } + + /// + /// Registers a service instance to the type given. + /// + /// + /// + [PublicAPI] + public static void AddService(TService service) + { + AddService(typeof(TService), service); + } + + /// + /// Registers a service instance to the type given. + /// + /// + /// + [PublicAPI] + public static void AddService(Type tService, object service) + { + Instance.AddServiceInstance(tService, service); + } + + #endregion + + private readonly Dictionary m_Services = new Dictionary(); + private readonly SafeCriticalSection m_ServicesSection = new SafeCriticalSection(); + + #region Methods + + private object GetServiceInstance(Type tService) + { + try + { + m_ServicesSection.Enter(); + + object service; + if (m_Services.TryGetValue(tService, out service) && service != null) + return service; + throw new ServiceNotFoundException(tService); + } + finally + { + m_ServicesSection.Leave(); + } + } + + [PublicAPI] + private void AddServiceInstance(Type tService, object service) + { + m_ServicesSection.Enter(); + m_Services[tService] = service; + m_ServicesSection.Leave(); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + try + { + m_ServicesSection.Enter(); + foreach (object service in m_Services.Values.Distinct()) + { + if (!(service is IDisposable)) + continue; + ((IDisposable)service).Dispose(); + } + m_Services.Clear(); + } + finally + { + m_ServicesSection.Leave(); + } + } + + public static void DisposeStatic() + { + if (s_Instance != null) + s_Instance.Dispose(); + s_Instance = null; + } + + #endregion + } +} diff --git a/ICD.Common/Utils/AttributeUtils.cs b/ICD.Common/Utils/AttributeUtils.cs new file mode 100644 index 0000000..85492ab --- /dev/null +++ b/ICD.Common/Utils/AttributeUtils.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if SIMPLSHARP +using Crestron.SimplSharp.Reflection; +#else +using System.Reflection; +#endif +using ICD.Common.Properties; +using ICD.Common.Services; +using ICD.Common.Services.Logging; +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils +{ + /// + /// Utility methods for browsing code attributes. + /// Provides some basic caching for faster subsequent searches. + /// + public static class AttributeUtils + { + // Avoid caching the same assembly multiple times. + private static readonly IcdHashSet s_CachedAssemblies; + private static readonly IcdHashSet s_CachedTypes; + + private static readonly Dictionary s_AttributeToMethodCache; + private static readonly Dictionary> s_TypeToAttributesCache; + + /// + /// Constructor. + /// + static AttributeUtils() + { + s_CachedAssemblies = new IcdHashSet(); + s_CachedTypes = new IcdHashSet(); + + s_AttributeToMethodCache = new Dictionary(); + s_TypeToAttributesCache = new Dictionary>(); + } + + #region Caching + + /// + /// Pre-emptively caches the given assemblies for lookup. + /// + /// + public static void CacheAssemblies(IEnumerable assemblies) + { + if (assemblies == null) + throw new ArgumentNullException("assemblies"); + + foreach (Assembly assembly in assemblies) + CacheAssembly(assembly); + } + + /// + /// Pre-emptively caches the given assembly for lookup. + /// + /// + public static void CacheAssembly(Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException("assembly"); + + if (s_CachedAssemblies.Contains(assembly)) + return; + s_CachedAssemblies.Add(assembly); + +#if SIMPLSHARP + CType[] types = new CType[0]; +#else + Type[] types = new Type[0]; +#endif + try + { + types = assembly.GetTypes(); + } + catch (TypeLoadException e) + { + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Error, e, "Failed to cache assembly {0} - {1}", assembly.GetName().Name, + e.Message); + } + + foreach (var type in types) + CacheType(type); + } + + /// + /// Pre-emptively caches the given type for lookup. + /// + /// +#if SIMPLSHARP + public static void CacheType(CType type) +#else + public static void CacheType(Type type) +#endif + { + if (type == null) + throw new ArgumentNullException("type"); + + if (s_CachedTypes.Contains(type)) + return; + s_CachedTypes.Add(type); + + MethodInfo[] methods; + + try + { +#if SIMPLSHARP + s_TypeToAttributesCache[type] = new IcdHashSet(type.GetCustomAttributes(false)); + methods = type.GetMethods(); +#else + s_TypeToAttributesCache[type] = new IcdHashSet(type.GetTypeInfo().GetCustomAttributes(false)); + methods = type.GetTypeInfo().GetMethods(); +#endif + } + // GetMethods for Open Generic Types is not supported. + catch (NotSupportedException) + { + return; + } + // Not sure why this happens :/ + catch (InvalidProgramException) + { + return; + } + + foreach (MethodInfo method in methods) + CacheMethod(method); + } + + /// + /// Caches the method. + /// + /// + private static void CacheMethod(MethodInfo method) + { + if (method == null) + throw new ArgumentNullException("method"); + + foreach (Attribute attribute in method.GetCustomAttributes(false)) + s_AttributeToMethodCache[attribute] = method; + } + + #endregion + + #region Lookup + + /// + /// Gets the first attribute on the given class type matching the generic type. + /// + /// + /// + /// + [CanBeNull] + public static T GetClassAttribute(Type type) + where T : Attribute + { + if (type == null) + throw new ArgumentNullException("type"); + + return GetClassAttributes(type).FirstOrDefault(); + } + + /// + /// Gets the attributes on the given class type matching the generic type. + /// + /// + /// + /// + public static IEnumerable GetClassAttributes(Type type) + where T : Attribute + { + if (type == null) + throw new ArgumentNullException("type"); + + return GetClassAttributes(type).OfType(); + } + + /// + /// Gets the attributes on the given class. + /// + /// + /// + public static IEnumerable GetClassAttributes(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + CacheType(type); + + return s_TypeToAttributesCache.ContainsKey(type) + ? s_TypeToAttributesCache[type].ToArray() + : Enumerable.Empty(); + } + + /// + /// Gets all of the cached method attributes of the given type. + /// + /// + /// + public static IEnumerable GetMethodAttributes() + where T : Attribute + { + return s_AttributeToMethodCache.Select(p => p.Key) + .OfType() + .ToArray(); + } + + /// + /// Gets the cached method for the given attribute. + /// + /// + /// + public static MethodInfo GetMethod(Attribute attribute) + { + if (attribute == null) + throw new ArgumentNullException("attribute"); + + return s_AttributeToMethodCache[attribute]; + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Collections/IcdHashSet.cs b/ICD.Common/Utils/Collections/IcdHashSet.cs new file mode 100644 index 0000000..e1d9fa2 --- /dev/null +++ b/ICD.Common/Utils/Collections/IcdHashSet.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Collections +{ + /// + /// A collection containing only unique items. + /// + /// + public sealed class IcdHashSet : ICollection + { + private readonly Dictionary m_Dict; + + #region Properties + + /// + /// Returns a new, empty hashset. + /// + [PublicAPI] + public static IcdHashSet NullSet { get { return new IcdHashSet(); } } + + /// + /// Gets the number of items contained in the . + /// + /// + /// The number of items contained in the . + /// + public int Count { get { return m_Dict.Count; } } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// true if the is read-only; otherwise, false. + /// + public bool IsReadOnly { get { return false; } } + + #endregion + + #region Constructors + + /// + /// Constructor. + /// + public IcdHashSet() + { + m_Dict = new Dictionary(); + } + + /// + /// Constructor. + /// + /// + public IcdHashSet(IEnumerable items) + : this() + { + if (items == null) + return; + + AddRange(items); + } + + #endregion + + #region Methods + + [PublicAPI] + public IcdHashSet Union(IEnumerable set) + { + IcdHashSet unionSet = new IcdHashSet(this); + + if (set == null) + return unionSet; + + unionSet.AddRange(set); + + return unionSet; + } + + [PublicAPI] + public IcdHashSet Subtract(IEnumerable set) + { + IcdHashSet subtractSet = new IcdHashSet(this); + + if (set == null) + return subtractSet; + + foreach (T item in set) + subtractSet.Remove(item); + + return subtractSet; + } + + [PublicAPI] + public bool IsSubsetOf(IcdHashSet set) + { + IcdHashSet setToCompare = set ?? NullSet; + return this.All(setToCompare.Contains); + } + + [PublicAPI] + public IcdHashSet Intersection(IcdHashSet set) + { + IcdHashSet intersectionSet = NullSet; + + if (set == null) + return intersectionSet; + + foreach (T item in this.Where(set.Contains)) + intersectionSet.Add(item); + + foreach (T item in set.Where(Contains)) + intersectionSet.Add(item); + + return intersectionSet; + } + + /// + /// Returns items that are not common between both sets. + /// + /// + /// + [PublicAPI] + public IcdHashSet NonIntersection(IcdHashSet set) + { + return Subtract(set).Union(set.Subtract(this)); + } + + [PublicAPI] + public bool IsProperSubsetOf(IcdHashSet set) + { + IcdHashSet setToCompare = set ?? NullSet; + + // Is a proper subset if A is a subset of B and A != B + return (IsSubsetOf(setToCompare) && !setToCompare.IsSubsetOf(this)); + } + + [PublicAPI] + public bool IsSupersetOf(IcdHashSet set) + { + IcdHashSet setToCompare = set ?? NullSet; + return setToCompare.IsSubsetOf(this); + } + + [PublicAPI] + public bool IsProperSupersetOf(IcdHashSet set) + { + IcdHashSet setToCompare = set ?? NullSet; + + // B is a proper superset of A if B is a superset of A and A != B + return (IsSupersetOf(setToCompare) && !setToCompare.IsSupersetOf(this)); + } + + #endregion + + #region ICollection Members + + /// + /// Adds the item to the collection. Returns false if the item already exists. + /// + /// + /// + public bool Add(T item) + { +// ReSharper disable CompareNonConstrainedGenericWithNull + if (item == null) +// ReSharper restore CompareNonConstrainedGenericWithNull + throw new ArgumentNullException("item"); + + if (m_Dict.ContainsKey(item)) + return false; + + m_Dict[item] = null; + return true; + } + + /// + /// Adds the item to the collection. + /// + /// + void ICollection.Add(T item) + { + Add(item); + } + + /// + /// Adds each of the items in the sequence to the collection. + /// + /// + public void AddRange(IEnumerable items) + { + foreach (T item in items) + Add(item); + } + + /// + /// Removes all items from the . + /// + /// The is read-only. + public void Clear() + { + m_Dict.Clear(); + } + + /// + /// Returns true if the IcdHashSet contains the given item. + /// + /// + /// + public bool Contains(T item) + { + return m_Dict.ContainsKey(item); + } + + /// + /// Copies the items of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the items copied from . The must have zero-based indexing.The zero-based index in at which copying begins. is null. is less than 0. is multidimensional.-or- is equal to or greater than the length of .-or-The number of items in the source is greater than the available space from to the end of the destination .-or-Type T cannot be cast automatically to the type of the destination . + public void CopyTo(T[] array, int arrayIndex) + { + m_Dict.Keys.CopyTo(array, arrayIndex); + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + /// The object to remove from the .The is read-only. + public bool Remove(T item) + { + return m_Dict.Remove(item); + } + + #endregion + + #region Implementation of IEnumerable + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() + { + return m_Dict.Keys.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Collections/ScrollQueue.cs b/ICD.Common/Utils/Collections/ScrollQueue.cs new file mode 100644 index 0000000..36729d6 --- /dev/null +++ b/ICD.Common/Utils/Collections/ScrollQueue.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Collections +{ + /// + /// ScrollQueue is a queue that never exceeds a predefined length. Old items + /// are removed as new items are added. + /// + /// + public sealed class ScrollQueue : IEnumerable, ICollection + { + private readonly LinkedList m_Collection; + private int m_MaxSize; + + private readonly SafeCriticalSection m_CollectionLock; + + #region Properties + + /// + /// Gets/sets the maximum size of the queue. + /// + [PublicAPI] + public int MaxSize + { + get { return m_MaxSize; } + set + { + if (value == m_MaxSize) + return; + + m_MaxSize = value; + + Trim(); + } + } + + /// + /// Gets the number of items in the collection. + /// + public int Count { get { return m_CollectionLock.Execute(() => m_Collection.Count); } } + + /// + /// The IsSynchronized Boolean property returns True if the + /// collection is designed to be thread safe; otherwise, it returns False. + /// + public bool IsSynchronized { get { return true; } } + + /// + /// The SyncRoot property returns an object, which is used for synchronizing + /// the collection. This returns the instance of the object or returns the + /// SyncRoot of other collections if the collection contains other collections. + /// + public object SyncRoot { get { return this; } } + + #endregion + + /// + /// Constructor. + /// + /// + public ScrollQueue(int maxSize) + { + m_CollectionLock = new SafeCriticalSection(); + m_Collection = new LinkedList(); + MaxSize = maxSize; + } + + #region Queue Methods + + /// + /// Clears all items from the queue. + /// + public void Clear() + { + m_CollectionLock.Execute(() => m_Collection.Clear()); + } + + /// + /// Appends the item to the queue, trims old items that exceed max length. + /// + /// + [PublicAPI] + public void Enqueue(TContents item) + { + m_CollectionLock.Execute(() => m_Collection.AddLast(item)); + Trim(); + } + + /// + /// Removes the oldest item from the queue. + /// + /// + [PublicAPI] + public TContents Dequeue() + { + m_CollectionLock.Enter(); + + try + { + TContents output = m_Collection.First.Value; + m_Collection.RemoveFirst(); + return output; + } + finally + { + m_CollectionLock.Leave(); + } + } + + /// + /// Returns the oldest item in the queue. + /// + /// + [PublicAPI] + public TContents Peek() + { + return m_CollectionLock.Execute(() => m_Collection.First.Value); + } + + #endregion + + #region Implemented Methods + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_CollectionLock.Execute(() => m_Collection.ToList().GetEnumerator()); + } + + void ICollection.CopyTo(Array myArr, int index) + { + m_CollectionLock.Enter(); + + try + { + foreach (TContents item in m_Collection) + { + myArr.SetValue(item, index); + index++; + } + } + finally + { + m_CollectionLock.Leave(); + } + } + + #endregion + + #region Private Methods + + /// + /// Removes items that fall outside of the max size. + /// + private void Trim() + { + m_CollectionLock.Enter(); + + try + { + while (Count > MaxSize) + m_Collection.RemoveFirst(); + } + finally + { + m_CollectionLock.Leave(); + } + } + + #endregion + } +} diff --git a/ICD.Common/Utils/CrestronUtils.cs b/ICD.Common/Utils/CrestronUtils.cs new file mode 100644 index 0000000..2a98e8a --- /dev/null +++ b/ICD.Common/Utils/CrestronUtils.cs @@ -0,0 +1,245 @@ +#if SIMPLSHARP +using System; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using ICD.Common.Properties; +using ICD.Common.Services; +using ICD.Common.Services.Logging; + +namespace ICD.Common.Utils +{ + public static class CrestronUtils + { + private const string MODEL_NAME_REGEX = @"^(\S*)"; + private const string MODEL_VERSION_REGEX = @" [[]v(\S*)"; + + private const string RAMFREE_COMMAND = "ramfree"; + private const string RAMFREE_DIGITS_REGEX = @"^(\d*)"; + + private static string s_VersionResult; + + #region Properties + + /// + /// Gets the default mac address of the processor. + /// + [PublicAPI] + public static string DefaultMacAddress + { + get + { + const CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET param = + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS; + const EthernetAdapterType type = EthernetAdapterType.EthernetLANAdapter; + short id = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(type); + return CrestronEthernetHelper.GetEthernetParameter(param, id); + } + } + + /// + /// Gets the version text from the console. + /// + private static string VersionResult + { + get + { + if (string.IsNullOrEmpty(s_VersionResult)) + { + if (!CrestronConsole.SendControlSystemCommand("version", ref s_VersionResult)) + { + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Warning, "{0} - Failed to send console command \"{1}\"", + typeof(CrestronUtils).Name, "version"); + } + } + + return s_VersionResult; + } + } + + /// + /// Gets the model name of the processor. + /// + [PublicAPI] + public static string ModelName + { + get + { + Regex regex = new Regex(MODEL_NAME_REGEX); + Match match = regex.Match(VersionResult); + + if (match.Success) + return match.Groups[1].Value; + + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Warning, "Unable to get model name from \"{0}\"", VersionResult); + return string.Empty; + } + } + + /// + /// Gets the processor firmware version. + /// + [PublicAPI] + public static Version ModelVersion + { + get + { + Regex regex = new Regex(MODEL_VERSION_REGEX); + Match match = regex.Match(VersionResult); + + if (match.Success) + return new Version(match.Groups[1].Value); + + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Warning, "Unable to get model version from \"{0}\"", VersionResult); + return new Version(0, 0); + } + } + + /// + /// Gets the ram usage in the range 0 - 1. + /// + public static float RamUsagePercent + { + get + { + string ramFree = GetRamFree(); + string digits = Regex.Matches(ramFree, RAMFREE_DIGITS_REGEX, RegexOptions.Multiline)[0].Groups[1].Value; + return float.Parse(digits) / 100.0f; + } + } + + /// + /// Gets the total number of bytes of physical memory. + /// + public static ulong RamTotalBytes + { + get + { + string ramFree = GetRamFree(); + string digits = Regex.Matches(ramFree, RAMFREE_DIGITS_REGEX, RegexOptions.Multiline)[1].Groups[1].Value; + return ulong.Parse(digits); + } + } + + /// + /// Gets the total number of bytes of physical memory being used by the control system. + /// + public static ulong RamUsedBytes + { + get + { + string ramFree = GetRamFree(); + string digits = Regex.Matches(ramFree, RAMFREE_DIGITS_REGEX, RegexOptions.Multiline)[2].Groups[1].Value; + return ulong.Parse(digits); + } + } + + /// + /// Gets the total number of bytes of physical memory not being used by the control system. + /// + public static ulong RamBytesFree + { + get + { + string ramFree = GetRamFree(); + string digits = Regex.Matches(ramFree, RAMFREE_DIGITS_REGEX, RegexOptions.Multiline)[3].Groups[1].Value; + return ulong.Parse(digits); + } + } + + /// + /// Gets the total number of bytes that can be reclaimed. + /// + public static ulong RamBytesReclaimable + { + get + { + string ramFree = GetRamFree(); + string digits = Regex.Matches(ramFree, RAMFREE_DIGITS_REGEX, RegexOptions.Multiline)[4].Groups[1].Value; + return ulong.Parse(digits); + } + } + + #endregion + + #region Methods + + /// + /// Restarts this program. + /// + [PublicAPI] + public static void RestartProgram() + { + string consoleResult = string.Empty; + string command = string.Format("progreset -p:{0:D2}", ProgramUtils.ProgramNumber); + CrestronConsole.SendControlSystemCommand(command, ref consoleResult); + } + + /// + /// Reboots the processor. + /// + [PublicAPI] + public static void Reboot() + { + string consoleResult = string.Empty; + CrestronConsole.SendControlSystemCommand("reboot", ref consoleResult); + } + + /// + /// Runs CrestronInvoke but catches any unhandled exceptions to prevent the program from terminating. + /// http://www.crestronlabs.com/showthread.php?12205-Exception-in-CrestronInvoke-thread-crashes-the-program + /// + /// + [PublicAPI] + public static object SafeInvoke(Action callback) + { + return SafeInvoke(unused => callback(), null); + } + + /// + /// Runs CrestronInvoke but catches any unhandled exceptions to prevent the program from terminating. + /// http://www.crestronlabs.com/showthread.php?12205-Exception-in-CrestronInvoke-thread-crashes-the-program + /// + /// + /// + /// + [PublicAPI] + public static object SafeInvoke(Action callback, T param) + { + return CrestronInvoke.BeginInvoke(unused => + { + try + { + callback(param); + } + catch (Exception e) + { + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Error, e, e.Message); + } + }, null); + } + + #endregion + + /// + /// Gets the result from the ramfree console command. + /// + /// + private static string GetRamFree() + { + string ramfree = null; + if (!CrestronConsole.SendControlSystemCommand(RAMFREE_COMMAND, ref ramfree)) + { + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Warning, "{0} - Failed to send console command \"{1}\"", + typeof(CrestronUtils).Name, RAMFREE_COMMAND); + } + return ramfree; + } + } +} + +#endif diff --git a/ICD.Common/Utils/EnumUtils.cs b/ICD.Common/Utils/EnumUtils.cs new file mode 100644 index 0000000..ef7ec7f --- /dev/null +++ b/ICD.Common/Utils/EnumUtils.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if SIMPLSHARP +using System.Globalization; +using Crestron.SimplSharp.Reflection; +#else +using System.Reflection; +#endif +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.Extensions; + +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. + /// + /// + private static bool IsEnum(Type type) + { + return type == typeof(Enum) || type +#if !SIMPLSHARP + .GetTypeInfo() +#endif + .IsEnum; + } + + /// + /// Returns true if the given type is an enum. + /// + /// + /// + private static bool IsEnum() + { + return IsEnum(typeof(T)); + } + + #region Values + + /// + /// Gets the underlying value of the enum. + /// + /// + /// + public static object GetUnderlyingValue(T value) + { + if (!IsEnum(typeof(T))) + throw new InvalidOperationException(string.Format("{0} is not an enum", value.GetType().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 (!IsEnum(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 (!IsEnum(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 (!IsEnum(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 (!IsEnum(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 (!IsEnum()) + 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 (!IsEnum(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 (!IsEnum()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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 (!IsEnum()) + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + 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()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + return HasMultipleFlags((int)(object)value); + } + + /// + /// Returns true if the enum has more than 1 flag set. + /// + /// + /// + private static bool HasMultipleFlags(int value) + { + return ((value & (value - 1)) != 0); + } + + #endregion + + #region Conversion + + /// + /// Shorthand for parsing string to enum. + /// + /// + /// + /// + /// + public static T Parse(string data, bool ignoreCase) + { + if (!IsEnum()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + try + { + return (T)Enum.Parse(typeof(T), data, ignoreCase); + } + catch (Exception e) + { + throw new FormatException( + string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), typeof(T).Name), e); + } + } + + /// + /// 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 (!IsEnum()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + result = default(T); + + try + { + result = Parse(data, ignoreCase); + return true; + } + catch (Exception) + { + return false; + } + } + + /// + /// Converts the given enum value to an Enum. + /// + /// + /// + /// + public static Enum ToEnum(T value) + { + if (!IsEnum()) + throw new ArgumentException(string.Format("{0} is not an enum", typeof(T).Name)); + + return ToEnum((object)value); + } + + /// + /// Converts the given enum value to an Enum. + /// + /// + /// + public static Enum ToEnum(object value) + { + return (Enum)value; + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Extensions/CollectionExtensions.cs b/ICD.Common/Utils/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000..19b7050 --- /dev/null +++ b/ICD.Common/Utils/Extensions/CollectionExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Extension methods for working with ICollections. + /// + public static class CollectionExtensions + { + /// + /// Removes items matching the predicate. + /// + /// + /// + /// + public static void RemoveAll(this ICollection extends, Func predicate) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (predicate == null) + throw new ArgumentNullException("predicate"); + + extends.RemoveAll(extends.Where(predicate).ToArray()); + } + + /// + /// Removes all of the items from the other collection. + /// + /// + /// + /// + public static void RemoveAll(this ICollection extends, IEnumerable other) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (other == null) + throw new ArgumentNullException("other"); + + foreach (T item in other) + extends.Remove(item); + } + } +} diff --git a/ICD.Common/Utils/Extensions/DateTimeExtensions.cs b/ICD.Common/Utils/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..701ae83 --- /dev/null +++ b/ICD.Common/Utils/Extensions/DateTimeExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Extension methods for DateTime. + /// + public static class DateTimeExtensions + { + /// + /// Gets a string representation of the DateTime with millisecond precision. + /// + /// + /// + public static string ToLongTimeStringWithMilliseconds(this DateTime extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + // Todo - Better handle different cultures + return extends.ToString("HH:mm:ss:fff"); + } + } +} diff --git a/ICD.Common/Utils/Extensions/DictionaryExtensions.cs b/ICD.Common/Utils/Extensions/DictionaryExtensions.cs new file mode 100644 index 0000000..622ae11 --- /dev/null +++ b/ICD.Common/Utils/Extensions/DictionaryExtensions.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Extensions +{ + public static class DictionaryExtensions + { + /// + /// Removes the first key with a value matching the given value. + /// + /// + /// + /// + /// + /// False if value is not found in the dictionary. + [PublicAPI] + public static bool RemoveValue(this IDictionary extends, TValue value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + try + { + TKey key = extends.GetKey(value); + return extends.Remove(key); + } + catch (ArgumentOutOfRangeException) + { + return false; + } + } + + /// + /// Removes all keys with the given value. + /// + /// + /// + /// + /// + [PublicAPI] + public static void RemoveAllValues(this IDictionary extends, TValue value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + foreach (TKey key in extends.GetKeys(value).ToArray()) + extends.Remove(key); + } + + /// + /// If the key is present in the dictionary return the value, otherwise returns default value. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static TValue GetDefault(this IDictionary extends, TKey key) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + // ReSharper disable once CompareNonConstrainedGenericWithNull + if (key == null) + throw new ArgumentNullException("key"); + + return extends.GetDefault(key, default(TValue)); + } + + /// + /// If the key is present in the dictionary return the value, otherwise return the default value. + /// + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static TValue GetDefault(this IDictionary extends, TKey key, TValue defaultValue) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + // ReSharper disable once CompareNonConstrainedGenericWithNull + if (key == null) + throw new ArgumentNullException("key"); + + return extends.ContainsKey(key) ? extends[key] : defaultValue; + } + + /// + /// If the key is present in the dictionary return the value, otherwise add the default value to the dictionary and return it. + /// + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static TValue GetOrAddDefault(this IDictionary extends, TKey key, + TValue defaultValue) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + // ReSharper disable once CompareNonConstrainedGenericWithNull + if (key == null) + throw new ArgumentNullException("key"); + + extends[key] = extends.GetDefault(key, defaultValue); + return extends[key]; + } + + /// + /// Gets a key for the given value. + /// + /// + /// + /// + /// + /// + /// The value does not exist in the dictionary. + [PublicAPI] + public static TKey GetKey(this IDictionary extends, TValue value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + try + { + return extends.GetKeys(value).First(); + } + catch (InvalidOperationException) + { + string message = string.Format("Unable to find Key with Value matching {0}", value); + throw new KeyNotFoundException(message); + } + } + + /// + /// Gets the keys that match the given value. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable GetKeys(this IDictionary extends, TValue value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.Where(kvp => EqualityComparer.Default.Equals(kvp.Value, value)) + .Select(kvp => kvp.Key); + } + + /// + /// Updates the dictionary with items from the other dictionary. + /// + /// + /// + /// + /// + [PublicAPI] + public static void Update(this IDictionary extends, IDictionary other) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (other == null) + throw new ArgumentNullException("other"); + + foreach (KeyValuePair pair in other) + extends[pair.Key] = pair.Value; + } + + /// + /// Adds the sequence of items to the dictionary. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static void AddRange(this IDictionary extends, IEnumerable items, + Func getKey) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + if (getKey == null) + throw new ArgumentNullException("getKey"); + + foreach (TValue item in items) + extends.Add(getKey(item), item); + } + + /// + /// Adds the sequence of items to the dictionary. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static void AddRange(this IDictionary extends, IEnumerable items, + Func getValue) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + if (getValue == null) + throw new ArgumentNullException("getValue"); + + foreach (TKey item in items) + extends.Add(item, getValue(item)); + } + + /// + /// Adds the sequence of items to the dictionary. + /// + /// + /// + /// + /// + [PublicAPI] + public static void AddRange(this Dictionary extends, + IEnumerable> items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + foreach (KeyValuePair item in items) + extends.Add(item.Key, item.Value); + } + + /// + /// Compares the keys and values of the dictionary to determine equality. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static bool DictionaryEqual(this IDictionary extends, + IDictionary other) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.DictionaryEqual(other, EqualityComparer.Default); + } + + /// + /// Compares the keys and values of the dictionary to determine equality. + /// + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static bool DictionaryEqual(this IDictionary extends, + IDictionary other, + IEqualityComparer valueComparer) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends == other) + return true; + if (other == null) + return false; + if (extends.Count != other.Count) + return false; + + foreach (KeyValuePair kvp in extends) + { + TValue secondValue; + if (!other.TryGetValue(kvp.Key, out secondValue)) + return false; + if (!valueComparer.Equals(kvp.Value, secondValue)) + return false; + } + return true; + } + + /// + /// Returns the KeyValuePairs in key order. + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable> OrderByKey( + this IEnumerable> extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.OrderBy(kvp => kvp.Key); + } + + /// + /// Returns a sequence of values ordered by the dictionary keys. + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable OrderValuesByKey(this IEnumerable> extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value); + } + } +} diff --git a/ICD.Common/Utils/Extensions/EnumExtensions.cs b/ICD.Common/Utils/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..3ec6d71 --- /dev/null +++ b/ICD.Common/Utils/Extensions/EnumExtensions.cs @@ -0,0 +1,71 @@ +using System; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Extensions +{ + public static class EnumExtensions + { + /// + /// Check to see if a flags enumeration has a specific flag set. + /// + /// Flags enumeration to check + /// Flag to check for + /// + [PublicAPI] + public static bool HasFlag(this Enum extends, Enum value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (EnumUtils.HasMultipleFlags(value)) + throw new ArgumentException("Value has multiple flags", "value"); + + return extends.HasFlags(value); + } + + /// + /// Check to see if a flags enumeration has all of the given flags set. + /// + /// + /// + /// + [PublicAPI] + public static bool HasFlags(this Enum extends, Enum value) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (value == null) + throw new ArgumentNullException("value"); + + // Not as good as the .NET 4 version of this function, but should be good enough + if (extends.GetType() != value.GetType()) + { + string message = string.Format("Enumeration type mismatch. The flag is of type '{0}', was expecting '{1}'.", + value.GetType(), extends.GetType()); + throw new ArgumentException(message); + } + + ulong num = Convert.ToUInt64(value); + return (Convert.ToUInt64(extends) & num) == num; + } + + /// + /// Casts the enum to the given type. + /// + /// + /// + /// + [PublicAPI] + public static T Cast(this Enum extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return (T)Enum.ToObject(typeof(T), extends); + } + } +} diff --git a/ICD.Common/Utils/Extensions/EnumerableExtensions.cs b/ICD.Common/Utils/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..cdf343b --- /dev/null +++ b/ICD.Common/Utils/Extensions/EnumerableExtensions.cs @@ -0,0 +1,717 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; +using ICD.Common.Utils.Collections; + +namespace ICD.Common.Utils.Extensions +{ + public static class EnumerableExtensions + { + /// + /// Returns the first item in the sequence. Returns the provided default item if there + /// are no elements in the sequence. + /// + /// + /// + /// + /// + public static T FirstOrDefault(this IEnumerable extends, T defaultItem) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.FirstOrDefault(i => true, defaultItem); + } + + /// + /// Returns the first element in the sequence matching the predicate. Returns the provided + /// default item if there are no elements matching the predicate in the sequence. + /// + /// + /// + /// + /// + /// + public static T FirstOrDefault(this IEnumerable extends, Func predicate, T defaultItem) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (predicate == null) + throw new ArgumentNullException("predicate"); + + foreach (T item in extends.Where(predicate)) + return item; + return defaultItem; + } + + /// + /// Returns true if there is at least 1 item in the sequence. + /// + /// + /// + /// Outputs the first item in the sequence. + /// + public static bool TryFirstOrDefault(this IEnumerable extends, out T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.TryFirstOrDefault(i => true, out item); + } + + /// + /// Returns true if there is at least 1 item in the sequence. + /// + /// + /// + /// + /// Outputs the first item in the sequence. + /// + public static bool TryFirstOrDefault(this IEnumerable extends, Func predicate, out T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (predicate == null) + throw new ArgumentNullException("predicate"); + + item = default(T); + + using (IEnumerator iterator = extends.GetEnumerator()) + { + while (iterator.MoveNext()) + { + if (!predicate(iterator.Current)) + continue; + + item = iterator.Current; + return true; + } + } + + return false; + } + + /// + /// Returns the true if an element with the given index is in the sequence. + /// + /// + /// + /// + /// + /// + public static bool TryElementAt(this IEnumerable extends, int index, out T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + item = default(T); + + if (index < 0) + return false; + + T[] itemArray = extends as T[] ?? extends.ToArray(); + if (index >= itemArray.Length) + return false; + + item = itemArray[index]; + return true; + } + + /// + /// Compares two sequences for identical values, ignoring order. + /// + /// + /// + /// + /// + public static bool ScrambledEquals(this IEnumerable extends, IEnumerable other) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends == null) + throw new ArgumentNullException("other"); + + return extends.ScrambledEquals(other, EqualityComparer.Default); + } + + /// + /// Compares two sequences for identical values, ignoring order. + /// + /// + /// + /// + /// + /// + public static bool ScrambledEquals(this IEnumerable extends, IEnumerable other, IEqualityComparer comparer) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends == null) + throw new ArgumentNullException("other"); + + if (comparer == null) + throw new ArgumentNullException("comparer"); + + Dictionary count = new Dictionary(comparer); + + foreach (T item in extends) + { + if (count.ContainsKey(item)) + count[item]++; + else + count.Add(item, 1); + } + + foreach (T item in other) + { + if (count.ContainsKey(item)) + count[item]--; + else + return false; + } + + return count.Values.All(c => c == 0); + } + + /// + /// Returns the index that matches the predicate. + /// + /// + /// + /// + /// + public static int FindIndex(this IEnumerable extends, Predicate match) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (match == null) + throw new ArgumentNullException("match"); + + int index = 0; + + foreach (T item in extends) + { + if (match(item)) + return index; + index++; + } + + return -1; + } + + /// + /// Allows for selection of multiple results for each item in the sequence. + /// + /// + /// + /// + /// + /// + public static IEnumerable SelectMulti(this IEnumerable extends, + params Func[] selectors) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (selectors == null) + throw new ArgumentNullException("selectors"); + + return extends.SelectMany(source => selectors, (source, selector) => selector(source)); + } + + /// + /// Enumerates each item in the sequence. + /// Useful for processing a LINQ query without creating a new collection. + /// + /// + /// + [PublicAPI] + public static void Execute(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + extends.ForEach(item => { }); + } + + /// + /// Performs the action for each item in the sequence. + /// + /// + /// + /// + [PublicAPI] + public static void ForEach(this IEnumerable extends, Action action) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (action == null) + throw new ArgumentNullException("action"); + + extends.ForEach((item, index) => action(item)); + } + + /// + /// Performs the action for each item in the sequence. + /// + /// + /// + /// + [PublicAPI] + public static void ForEach(this IEnumerable extends, Action action) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (action == null) + throw new ArgumentNullException("action"); + + int index = 0; + foreach (T item in extends) + action(item, index++); + } + +#if SIMPLSHARP + /// + /// Prepends the item to the start of the sequence. + /// + /// + /// + /// + /// + public static IEnumerable Prepend(this IEnumerable extends, T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.PrependMany(new[] {item}); + } +#endif + + /// + /// Prepends the items to the start of the sequence. + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable PrependMany(this IEnumerable extends, params T[] items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + foreach (T item in items) + yield return item; + foreach (T each in extends) + yield return each; + } + +#if SIMPLSHARP + /// + /// Appends the item to the end of the sequence. + /// + /// + /// + /// + /// + public static IEnumerable Append(this IEnumerable extends, T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.AppendMany(new[] {item}); + } +#endif + + /// + /// Appends the items to the end of the sequence. + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable AppendMany(this IEnumerable extends, params T[] items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + foreach (T each in extends) + yield return each; + foreach (T item in items) + yield return item; + } + + /// + /// Default ordering for the items in the sequence. + /// + /// + /// + /// + public static IEnumerable Order(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.OrderBy(i => i); + } + + /// + /// Returns every item in the sequence except the given item. + /// + /// + /// + /// + /// + public static IEnumerable Except(this IEnumerable extends, T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.Except(new[] {item}); + } + + /// + /// Returns the sequence as a IcdHashSet. + /// + /// + /// + /// + public static IcdHashSet ToHashSet(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return new IcdHashSet(extends); + } + + /// + /// Returns the sequence as an index:value dictionary. + /// + /// + /// + /// + [PublicAPI] + public static Dictionary ToDictionary(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + Dictionary output = new Dictionary(); + extends.ForEach((item, index) => output.Add(index, item)); + return output; + } + + /// + /// Returns the sequence as an index:value dictionary. + /// + /// + /// + /// + [PublicAPI] + public static Dictionary ToDictionaryUInt(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + Dictionary output = new Dictionary(); + extends.ForEach((item, index) => output.Add((uint)index, item)); + return output; + } + + /// + /// Turns an enumerable of KeyValuePairs back into a dictionary + /// + /// + /// + /// + /// + public static Dictionary ToDictionary(this IEnumerable> extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.ToDictionary(x => x.Key, x => x.Value); + } + + /// + /// Returns other if the list is empty. + /// Returns other if the list is non-empty and there are two different elements. + /// Returns the element of the list if it is non-empty and all elements are the same. + /// + /// + /// + /// + [PublicAPI] + public static T Unanimous(this IEnumerable extends, T other) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + T[] array = extends as T[] ?? extends.ToArray(); + return array.Unanimous() ? array.First() : other; + } + + /// + /// Returns false if the list is empty. + /// Returns false if the list is non-empty and there are two different elements. + /// Returns true if the list is non-empty and all elements are the same. + /// + /// + /// + [PublicAPI] + public static bool Unanimous(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + T[] array = extends as T[] ?? extends.ToArray(); + + if (array.Length == 0) + return false; + + T val = array.First(); + return array.All(x => EqualityComparer.Default.Equals(x, val)); + } + + /// + /// Partitions a sequence into sequences of the given length. + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable> Partition(this IEnumerable extends, int partitionSize) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + using (IEnumerator enumerator = extends.GetEnumerator()) + { + while (enumerator.MoveNext()) + yield return YieldBatchElements(enumerator, partitionSize - 1); + } + } + + private static IEnumerable YieldBatchElements(IEnumerator source, int partitionSize) + { + if (source == null) + throw new ArgumentNullException("source"); + + // Important to enumerate through partitionSize items before returning + // Otherwise enumerable.Partition(3).Skip(1) will do unwanted things. + List output = new List {source.Current}; + + for (int i = 0; i < partitionSize && source.MoveNext(); i++) + output.Add(source.Current); + + return output; + } + + /// + /// Returns the minimal element of the given sequence, based on + /// the given projection. + /// + /// + /// If more than one element has the minimal projected value, the first + /// one encountered will be returned. This overload uses the default comparer + /// for the projected type. This operator uses immediate execution, but + /// only buffers a single result (the current minimal element). + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// The minimal element, according to the projection. + /// or is null + /// is empty + [PublicAPI] + public static TSource MinBy(this IEnumerable source, + Func selector) + { + if (source == null) + throw new ArgumentNullException("source"); + + if (selector == null) + throw new ArgumentNullException("selector"); + + return source.MinBy(selector, Comparer.Default); + } + + /// + /// Returns the minimal element of the given sequence, based on + /// the given projection and the specified comparer for projected values. + /// + /// + /// If more than one element has the minimal projected value, the first + /// one encountered will be returned. This operator uses immediate execution, but + /// only buffers a single result (the current minimal element). + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// Comparer to use to compare projected values + /// The minimal element, according to the projection. + /// , + /// or is null + /// is empty + [PublicAPI] + public static TSource MinBy(this IEnumerable source, Func selector, + IComparer comparer) + { + if (source == null) + throw new ArgumentNullException("source"); + + if (selector == null) + throw new ArgumentNullException("selector"); + + if (comparer == null) + throw new ArgumentNullException("comparer"); + + using (IEnumerator sourceIterator = source.GetEnumerator()) + { + if (!sourceIterator.MoveNext()) + throw new InvalidOperationException("Sequence contains no elements"); + TSource min = sourceIterator.Current; + TKey minKey = selector(min); + while (sourceIterator.MoveNext()) + { + TSource candidate = sourceIterator.Current; + TKey candidateProjected = selector(candidate); + if (comparer.Compare(candidateProjected, minKey) >= 0) + continue; + + min = candidate; + minKey = candidateProjected; + } + return min; + } + } + + /// + /// Removes any null elements from an enumerable of nullable value types + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable ExceptNulls(this IEnumerable extends) + where T : struct + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.Where(e => e.HasValue).Select(e => e.Value); + } + + /// + /// Computes the sum of a sequence of byte values. + /// + /// + /// + [PublicAPI] + public static byte Sum(this IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return (byte)extends.Select(i => (int)i).Sum(); + } + + /// + /// Skips duplicate, consecutive items. + /// E.g. + /// [1, 2, 2, 3, 1, 1] + /// Becomes + /// [1, 2, 3, 1] + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Consolidate(IEnumerable extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return Consolidate(extends, Comparer.Default); + } + + /// + /// Skips duplicate, consecutive items. + /// E.g. + /// [1, 2, 2, 3, 1, 1] + /// Becomes + /// [1, 2, 3, 1] + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Consolidate(IEnumerable extends, IComparer comparer) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (comparer == null) + throw new ArgumentNullException("comparer"); + + bool first = true; + T last = default(T); + + foreach (T item in extends) + { + if (!first && comparer.Compare(last, item) != 0) + continue; + + first = false; + last = item; + yield return item; + } + } + + /// + /// Returns true if all items in the sequence match the predicate. + /// Returns false if the sequence is empty. + /// + /// + /// + /// + /// + [PublicAPI] + public static bool AnyAndAll(this IEnumerable extends, Func predicate) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (predicate == null) + throw new ArgumentNullException("predicate"); + + bool any = false; + foreach (T item in extends) + { + any = true; + if (!predicate(item)) + return false; + } + + return any; + } + } +} diff --git a/ICD.Common/Utils/Extensions/EventHandlerExtensions.cs b/ICD.Common/Utils/Extensions/EventHandlerExtensions.cs new file mode 100644 index 0000000..958cf38 --- /dev/null +++ b/ICD.Common/Utils/Extensions/EventHandlerExtensions.cs @@ -0,0 +1,35 @@ +using System; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Extension methods for EventHandlers. + /// + public static class EventHandlerExtensions + { + /// + /// Raises the event safely. Simply skips if the handler is null. + /// + /// + /// + public static void Raise(this EventHandler extends, object sender) + { + if (extends != null) + extends(sender, EventArgs.Empty); + } + + /// + /// Raises the event safely. Simply skips if the handler is null. + /// + /// + /// + /// + /// + public static void Raise(this EventHandler extends, object sender, T args) + where T : EventArgs + { + if (extends != null) + extends(sender, args); + } + } +} diff --git a/ICD.Common/Utils/Extensions/HashSetExtensions.cs b/ICD.Common/Utils/Extensions/HashSetExtensions.cs new file mode 100644 index 0000000..8de5e86 --- /dev/null +++ b/ICD.Common/Utils/Extensions/HashSetExtensions.cs @@ -0,0 +1,87 @@ +#if STANDARD +using ICD.Common.Properties; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ICD.NetStandard.Common.Utils.Extensions +{ + public static class HashSetExtensions + { + [PublicAPI] + public static HashSet Subtract(this HashSet extends, IEnumerable set) + { + HashSet subtractSet = new HashSet(extends); + + if (set == null) + return subtractSet; + + foreach (T item in set) + subtractSet.Remove(item); + + return subtractSet; + } + + [PublicAPI] + public static bool IsSubsetOf(this HashSet extends, HashSet set) + { + HashSet setToCompare = set ?? new HashSet(); + return extends.All(setToCompare.Contains); + } + + [PublicAPI] + public static HashSet Intersection(this HashSet extends, HashSet set) + { + HashSet intersectionSet = new HashSet(); + + if (set == null) + return intersectionSet; + + foreach (T item in extends.Where(set.Contains)) + intersectionSet.Add(item); + + foreach (T item in set.Where(extends.Contains)) + intersectionSet.Add(item); + + return intersectionSet; + } + + /// + /// Returns items that are not common between both sets. + /// + /// + /// + [PublicAPI] + public static HashSet NonIntersection(this HashSet extends, HashSet set) + { + return new HashSet(extends.Subtract(set).Union(set.Subtract(extends))); + } + + [PublicAPI] + public static bool IsProperSubsetOf(this HashSet extends, HashSet set) + { + HashSet setToCompare = set ?? new HashSet(); + + // Is a proper subset if A is a subset of B and A != B + return (extends.IsSubsetOf(setToCompare) && !setToCompare.IsSubsetOf(extends)); + } + + [PublicAPI] + public static bool IsSupersetOf(this HashSet extends, HashSet set) + { + HashSet setToCompare = set ?? new HashSet(); + return setToCompare.IsSubsetOf(extends); + } + + [PublicAPI] + public static bool IsProperSupersetOf(this HashSet extends, HashSet set) + { + HashSet setToCompare = set ?? new HashSet(); + + // B is a proper superset of A if B is a superset of A and A != B + return (extends.IsSupersetOf(setToCompare) && !setToCompare.IsSupersetOf(extends)); + } + } +} +#endif \ No newline at end of file diff --git a/ICD.Common/Utils/Extensions/JsonExtensions.cs b/ICD.Common/Utils/Extensions/JsonExtensions.cs new file mode 100644 index 0000000..22ca920 --- /dev/null +++ b/ICD.Common/Utils/Extensions/JsonExtensions.cs @@ -0,0 +1,107 @@ +using System; +using ICD.Common.Properties; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Extension methods for working with JSON. + /// + public static class JsonExtensions + { + /// + /// Writes the object value. + /// + /// + /// + /// + /// + [PublicAPI] + public static void WriteObject(this JsonWriter extends, object value, JsonSerializer serializer, + JsonConverter converter) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (serializer == null) + throw new ArgumentNullException("serializer"); + + if (converter == null) + throw new ArgumentNullException("converter"); + + JObject jObject = JObject.FromObject(value, serializer); + jObject.WriteTo(extends, converter); + } + + /// + /// Gets the current value as an integer. + /// + /// + /// + [PublicAPI] + public static int GetValueAsInt(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends.TokenType == JsonToken.Integer) + return (int)(long)extends.Value; + + string message = string.Format("Token {0} {1} is not {2}", extends.TokenType, extends.Value, JsonToken.Integer); + throw new InvalidCastException(message); + } + + /// + /// Gets the current value as a string. + /// + /// + /// + [PublicAPI] + public static string GetValueAsString(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends.TokenType == JsonToken.String || extends.TokenType == JsonToken.Null) + return extends.Value as string; + + string message = string.Format("Token {0} {1} is not {2}", extends.TokenType, extends.Value, JsonToken.String); + throw new InvalidCastException(message); + } + + /// + /// Gets the current value as a bool. + /// + /// + /// + [PublicAPI] + public static bool GetValueAsBool(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends.TokenType == JsonToken.Boolean) + return (bool)extends.Value; + + string message = string.Format("Token {0} {1} is not {2}", extends.TokenType, extends.Value, JsonToken.Boolean); + throw new InvalidCastException(message); + } + + /// + /// Gets the current value as an enum. + /// + /// + /// + [PublicAPI] + public static T GetValueAsEnum(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends.TokenType == JsonToken.String) + return EnumUtils.Parse(extends.GetValueAsString(), true); + return (T)(object)extends.GetValueAsInt(); + } + } +} diff --git a/ICD.Common/Utils/Extensions/QueueExtensions.cs b/ICD.Common/Utils/Extensions/QueueExtensions.cs new file mode 100644 index 0000000..b317b32 --- /dev/null +++ b/ICD.Common/Utils/Extensions/QueueExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace ICD.Common.Utils.Extensions +{ + public static class QueueExtensions + { + /// + /// Enqueues each item in the sequence. + /// + /// + /// + /// + public static void EnqueueRange(this Queue extends, IEnumerable items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + foreach (T item in items) + extends.Enqueue(item); + } + + /// + /// Dequeues the next item in the queue. Returns false if the queue is empty. + /// + /// + /// + /// + /// + public static bool Dequeue(this Queue extends, out T item) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + item = default(T); + + if (extends.Count == 0) + return false; + + item = extends.Dequeue(); + return true; + } + } +} diff --git a/ICD.Common/Utils/Extensions/ReflectionExtensions.cs b/ICD.Common/Utils/Extensions/ReflectionExtensions.cs new file mode 100644 index 0000000..777e545 --- /dev/null +++ b/ICD.Common/Utils/Extensions/ReflectionExtensions.cs @@ -0,0 +1,32 @@ +#if SIMPLSHARP +using System; +using System.Collections.Generic; +using System.Linq; + +using Crestron.SimplSharp.Reflection; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Extension methods for use with reflection objects. + /// + public static class ReflectionExtensions + { + /// + /// Returns the custom attributes attached to the member. + /// + /// + /// + /// + /// + public static IEnumerable GetCustomAttributes(this MemberInfo extends, bool inherits) + where T : Attribute + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.GetCustomAttributes(typeof(T), inherits).Cast(); + } + } +} +#endif diff --git a/ICD.Common/Utils/Extensions/StringBuilderExtensions.cs b/ICD.Common/Utils/Extensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..a4e97f3 --- /dev/null +++ b/ICD.Common/Utils/Extensions/StringBuilderExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Text; + +namespace ICD.Common.Utils.Extensions +{ + public static class StringBuilderExtensions + { + /// + /// Empties the StringBuilder. + /// + /// + public static void Clear(this StringBuilder extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + extends.Remove(0, extends.Length); + } + + /// + /// Returns the current string value of the StringBuilder and clears the + /// StringBuilder for further use. + /// + /// + /// + public static string Pop(this StringBuilder extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string output = extends.ToString(); + extends.Clear(); + return output; + } + } +} diff --git a/ICD.Common/Utils/Extensions/StringExtensions.cs b/ICD.Common/Utils/Extensions/StringExtensions.cs new file mode 100644 index 0000000..88c2030 --- /dev/null +++ b/ICD.Common/Utils/Extensions/StringExtensions.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Extensions +{ + public static class StringExtensions + { + /// + /// Returns the first appearance of an item from the given sequence. + /// + /// + /// + /// + /// + [PublicAPI] + public static int IndexOf(this string extends, IEnumerable items, out string first) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + int index = -1; + first = null; + + foreach (string item in items) + { + int thisIndex = extends.IndexOf(item, StringComparison.Ordinal); + if (thisIndex == -1) + continue; + + if (index != -1 && thisIndex >= index) + continue; + + index = thisIndex; + first = item; + } + + return index; + } + + /// + /// Returns true if the string starts with the given character. + /// + /// + /// + /// + [PublicAPI] + public static bool StartsWith(this string extends, char character) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.StartsWith(character.ToString()); + } + + /// + /// Returns true if the string ends with the given character. + /// + /// + /// + /// + [PublicAPI] + public static bool EndsWith(this string extends, char character) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.EndsWith(character.ToString()); + } + + /// + /// Splits the string by the given delimiter, returning up to the given number of substrings. + /// E.g. "a:b:c".Split(':', 2) returns ["a", "b:c"] + /// + /// + /// + /// + /// + public static IEnumerable Split(this string extends, char delimeter, int count) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (count < 2) + { + yield return extends; + yield break; + } + + int index = extends.IndexOf(delimeter); + if (index < 0) + { + yield return extends; + yield break; + } + + string first = extends.Substring(0, index); + string second = extends.Substring(index + 1); + count--; + + yield return first; + foreach (string item in second.Split(delimeter, count)) + yield return item; + } + + /// + /// Splits a string into chunks of the given length. + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Split(this string extends, int chunkSize) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (chunkSize <= 0) + throw new InvalidOperationException("chunkSize must be greater than 0"); + + return Enumerable.Range(0, extends.Length / chunkSize) + .Select(i => extends.Substring(i * chunkSize, chunkSize)); + } + + /// + /// Splits a string by a given substring. + /// Taken from + /// https://social.msdn.microsoft.com/Forums/en-US/914a350f-e0e9-45e0-91a4-6b4b2168e780/string-split-function + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Split(this string extends, string delimeter) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (delimeter == null) + throw new ArgumentNullException("delimeter"); + + int dSum = 0; + int sSum = 0; + int length = extends.Length; + int delimiterLength = delimeter.Length; + + if (delimiterLength == 0 || length == 0 || length < delimiterLength) + { + yield return extends; + yield break; + } + + char[] cd = delimeter.ToCharArray(); + char[] cs = extends.ToCharArray(); + + for (int i = 0; i < delimiterLength; i++) + { + dSum += cd[i]; + sSum += cs[i]; + } + + int start = 0; + for (int i = start; i < length - delimiterLength; i++) + { + if (i >= start && dSum == sSum && extends.Substring(i, delimiterLength) == delimeter) + { + yield return extends.Substring(start, i - start); + start = i + delimiterLength; + } + + sSum += cs[i + delimiterLength] - cs[i]; + } + + if (dSum == sSum && extends.Substring(length - delimiterLength, delimiterLength) == delimeter) + { + yield return extends.Substring(start, length - delimiterLength - start); + yield return string.Empty; + } + else + yield return extends.Substring(start, length - start); + } + + /// + /// Splits a string by the given substrings. + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Split(this string extends, IEnumerable delimeters) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (delimeters == null) + throw new ArgumentNullException("delimeters"); + + string[] delimitersArray = delimeters as string[] ?? delimeters.ToArray(); + return delimitersArray.Length == 0 + ? new[] {extends} + : extends.Split(delimitersArray.First()) + .SelectMany(s => s.Split(delimitersArray.Skip(1))) + .Where(s => !string.IsNullOrEmpty(s)); + } + + /// + /// Removes whitespace from the string. + /// + /// + /// + [PublicAPI] + public static string RemoveWhitespace(this string extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return new string(extends.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + } + + /// + /// Removes the given characters from the string. + /// + /// + /// + /// + [PublicAPI] + public static string Remove(this string extends, IEnumerable characters) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (characters == null) + throw new ArgumentNullException("characters"); + + return new string(extends.Where(c => !characters.Contains(c)).ToArray()); + } + + /// + /// Returns true if the string only contains numbers. + /// + /// + /// + [PublicAPI] + public static bool IsNumeric(this string extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.All(char.IsDigit); + } + } +} diff --git a/ICD.Common/Utils/Extensions/TimeSpanExtensions.cs b/ICD.Common/Utils/Extensions/TimeSpanExtensions.cs new file mode 100644 index 0000000..b64e761 --- /dev/null +++ b/ICD.Common/Utils/Extensions/TimeSpanExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; + +namespace ICD.Common.Utils.Extensions +{ + public static class TimeSpanExtensions + { + public static string ToReadableString(this TimeSpan extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + StringBuilder builder = new StringBuilder(); + + if (extends.Days > 0) + builder.AppendFormat("{0} days, ", extends.Days); + if (extends.Hours > 0) + builder.AppendFormat("{0} hours, ", extends.Hours); + if (extends.Minutes > 0) + builder.AppendFormat("{0} minutes, ", extends.Minutes); + if (extends.Seconds > 0) + builder.AppendFormat("{0} seconds, ", extends.Seconds); + if (extends.Milliseconds > 0) + { + builder.AppendFormat("{0}.{1} ms", extends.Milliseconds, + ((double)extends.Ticks / TimeSpan.TicksPerMillisecond) - extends.TotalMilliseconds); + } + + return builder.ToString(); + } + } +} diff --git a/ICD.Common/Utils/Extensions/TypeExtensions.cs b/ICD.Common/Utils/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..0d45a4b --- /dev/null +++ b/ICD.Common/Utils/Extensions/TypeExtensions.cs @@ -0,0 +1,17 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp; +#else +using System.Reflection; +#endif + +namespace ICD.Common.Utils.Extensions +{ + public static class TypeExtensions + { + public static bool IsAssignableTo(this Type from, Type to) + { + return to.IsAssignableFrom(from); + } + } +} \ No newline at end of file diff --git a/ICD.Common/Utils/IO/IcdDirectory.cs b/ICD.Common/Utils/IO/IcdDirectory.cs new file mode 100644 index 0000000..64add31 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdDirectory.cs @@ -0,0 +1,69 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public static class IcdDirectory + { + public static string GetApplicationDirectory() + { +#if SIMPLSHARP + return Directory.GetApplicationDirectory(); +#else + return Directory.GetCurrentDirectory(); +#endif + } + + public static bool Exists(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Directory.Exists(path); + } + + public static string[] GetFiles(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Directory.GetFiles(path); + } + + public static string[] GetDirectories(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Directory.GetDirectories(path); + } + + public static void Delete(string path, bool recursive) + { + if (path == null) + throw new ArgumentNullException("path"); + + Directory.Delete(path, recursive); + } + + public static void CreateDirectory(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + Directory.CreateDirectory(path); + } + + public static string GetDirectoryRoot(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Directory.GetDirectoryRoot(path); + } + } +} diff --git a/ICD.Common/Utils/IO/IcdEncodingStringWriter.cs b/ICD.Common/Utils/IO/IcdEncodingStringWriter.cs new file mode 100644 index 0000000..6ba696a --- /dev/null +++ b/ICD.Common/Utils/IO/IcdEncodingStringWriter.cs @@ -0,0 +1,39 @@ +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif +using System; +using System.Text; + +namespace ICD.Common.Utils.IO +{ + public sealed class IcdEncodingStringWriter : IcdStringWriter + { + public IcdEncodingStringWriter(StringBuilder output, Encoding encoding) + : base(new EncodingStringWriter(output, encoding)) + { + } + + private sealed class EncodingStringWriter : StringWriter + { + private readonly Encoding m_Encoding; + + public override Encoding Encoding { get { return m_Encoding; } } + + /// + /// Constructor. + /// + /// + /// + public EncodingStringWriter(StringBuilder builder, Encoding encoding) + : base(builder) + { + if (encoding == null) + throw new ArgumentNullException("encoding"); + + m_Encoding = encoding; + } + } + } +} diff --git a/ICD.Common/Utils/IO/IcdFile.cs b/ICD.Common/Utils/IO/IcdFile.cs new file mode 100644 index 0000000..36ceca7 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdFile.cs @@ -0,0 +1,60 @@ +using ICD.Common.Properties; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif +using System; +using System.Text; + +namespace ICD.Common.Utils.IO +{ + public static class IcdFile + { + [PublicAPI] + public static string ReadToEnd(string path, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + + if (encoding == null) + throw new ArgumentNullException("encoding"); + +#if SIMPLSHARP + return File.ReadToEnd(path, encoding); +#else + return File.ReadAllText(path, encoding); +#endif + } + + [PublicAPI] + public static bool Exists(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return File.Exists(path); + } + + [PublicAPI] + public static void Copy(string pathFrom, string pathTo) + { + if (pathFrom == null) + throw new ArgumentNullException("pathFrom"); + + if (pathTo == null) + throw new ArgumentNullException("pathTo"); + + File.Copy(pathFrom, pathTo); + } + + [PublicAPI] + public static void Delete(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + File.Delete(path); + } + } +} diff --git a/ICD.Common/Utils/IO/IcdFileStream.cs b/ICD.Common/Utils/IO/IcdFileStream.cs new file mode 100644 index 0000000..83ddb78 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdFileStream.cs @@ -0,0 +1,47 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public sealed class IcdFileStream : IcdStream + { + public int Position { get; set; } + + public FileStream WrappedFileStream { get { return WrappedStream as FileStream; } } + + /// + /// Constructor. + /// + /// + public IcdFileStream(FileStream fileStrean) + : base(fileStrean) + { + } + + public static IcdFileStream OpenWrite(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return new IcdFileStream(File.OpenWrite(path)); + } + + public void Flush() + { + WrappedFileStream.Flush(); + } + + public void Close() + { +#if SIMPLSHARP + WrappedFileStream.Close(); +#else + WrappedFileStream.Dispose(); +#endif + } + } +} diff --git a/ICD.Common/Utils/IO/IcdMemoryStream.cs b/ICD.Common/Utils/IO/IcdMemoryStream.cs new file mode 100644 index 0000000..46eaa6a --- /dev/null +++ b/ICD.Common/Utils/IO/IcdMemoryStream.cs @@ -0,0 +1,47 @@ +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; + +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public sealed class IcdMemoryStream : IcdStream + { + public int Position { get; set; } + + public MemoryStream WrappedMemoryStream { get { return WrappedStream as MemoryStream; } } + + /// + /// Constructor. + /// + public IcdMemoryStream() + : this(new MemoryStream()) + { + } + + /// + /// Constructor. + /// + /// + public IcdMemoryStream(MemoryStream memoryStream) + : base(memoryStream) + { + } + + public void Flush() + { + WrappedMemoryStream.Flush(); + } + + public void Close() + { +#if SIMPLSHARP + WrappedMemoryStream.Close(); +#else + WrappedMemoryStream.Dispose(); +#endif + } + } +} diff --git a/ICD.Common/Utils/IO/IcdPath.cs b/ICD.Common/Utils/IO/IcdPath.cs new file mode 100644 index 0000000..6ec3f9a --- /dev/null +++ b/ICD.Common/Utils/IO/IcdPath.cs @@ -0,0 +1,58 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public static class IcdPath + { + public static string GetFileNameWithoutExtension(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Path.GetFileNameWithoutExtension(path); + } + + public static string GetDirectoryName(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Path.GetDirectoryName(path); + } + + public static string GetExtension(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + return Path.GetExtension(path); + } + + public static string Combine(string a, string b) + { + if (a == null) + throw new ArgumentNullException("a"); + + if (b == null) + throw new ArgumentNullException("b"); + + return Path.Combine(a, b); + } + + public static string ChangeExtension(string path, string ext) + { + if (path == null) + throw new ArgumentNullException("path"); + + if (ext == null) + throw new ArgumentNullException("ext"); + + return Path.ChangeExtension(path, ext); + } + } +} diff --git a/ICD.Common/Utils/IO/IcdStream.cs b/ICD.Common/Utils/IO/IcdStream.cs new file mode 100644 index 0000000..a415110 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdStream.cs @@ -0,0 +1,33 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public class IcdStream : IDisposable + { + private readonly Stream m_Stream; + + public Stream WrappedStream { get { return m_Stream; } } + + /// + /// Constructor. + /// + /// + public IcdStream(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + m_Stream = stream; + } + + public void Dispose() + { + m_Stream.Dispose(); + } + } +} diff --git a/ICD.Common/Utils/IO/IcdStreamReader.cs b/ICD.Common/Utils/IO/IcdStreamReader.cs new file mode 100644 index 0000000..030f50e --- /dev/null +++ b/ICD.Common/Utils/IO/IcdStreamReader.cs @@ -0,0 +1,41 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#elif STANDARD +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public sealed class IcdStreamReader : IDisposable + { + private readonly StreamReader m_StreamReader; + + /// + /// Constructor. + /// + /// + public IcdStreamReader(IcdMemoryStream memoryStream) + { + if (memoryStream == null) + throw new ArgumentNullException("memoryStream"); + + m_StreamReader = new StreamReader(memoryStream.WrappedMemoryStream); + } + + ~IcdStreamReader() + { + Dispose(); + } + + public string ReadToEnd() + { + return m_StreamReader.ReadToEnd(); + } + + public void Dispose() + { + m_StreamReader.Dispose(); + } + } +} diff --git a/ICD.Common/Utils/IO/IcdStringReader.cs b/ICD.Common/Utils/IO/IcdStringReader.cs new file mode 100644 index 0000000..9a9e847 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdStringReader.cs @@ -0,0 +1,24 @@ +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; + +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public sealed class IcdStringReader : IcdTextReader + { + public StringReader WrappedStringReader { get { return WrappedTextReader as StringReader; } } + + public IcdStringReader(string value) + : this(new StringReader(value)) + { + } + + private IcdStringReader(StringReader stringReader) + : base(stringReader) + { + } + } +} diff --git a/ICD.Common/Utils/IO/IcdStringWriter.cs b/ICD.Common/Utils/IO/IcdStringWriter.cs new file mode 100644 index 0000000..f7927a3 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdStringWriter.cs @@ -0,0 +1,32 @@ +using System.Text; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public class IcdStringWriter : IcdTextWriter + { + public StringWriter WrappedStringWriter { get { return WrappedTextWriter as StringWriter; } } + + /// + /// Constructor. + /// + /// + public IcdStringWriter(StringWriter stringWriter) + : base(stringWriter) + { + } + + /// + /// Constructor. + /// + /// + public IcdStringWriter(StringBuilder stringBuilder) + : this(new StringWriter(stringBuilder)) + { + } + } +} diff --git a/ICD.Common/Utils/IO/IcdTextReader.cs b/ICD.Common/Utils/IO/IcdTextReader.cs new file mode 100644 index 0000000..6c2285f --- /dev/null +++ b/ICD.Common/Utils/IO/IcdTextReader.cs @@ -0,0 +1,21 @@ +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; + +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public class IcdTextReader + { + private readonly TextReader m_TextReader; + + public TextReader WrappedTextReader { get { return m_TextReader; } } + + protected IcdTextReader(TextReader textReader) + { + m_TextReader = textReader; + } + } +} diff --git a/ICD.Common/Utils/IO/IcdTextWriter.cs b/ICD.Common/Utils/IO/IcdTextWriter.cs new file mode 100644 index 0000000..c90eb81 --- /dev/null +++ b/ICD.Common/Utils/IO/IcdTextWriter.cs @@ -0,0 +1,38 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif + +namespace ICD.Common.Utils.IO +{ + public class IcdTextWriter : IDisposable + { + private readonly TextWriter m_TextWriter; + + public TextWriter WrappedTextWriter { get { return m_TextWriter; } } + + /// + /// Constructor. + /// + /// + protected IcdTextWriter(TextWriter textWriter) + { + if (textWriter == null) + throw new ArgumentNullException("textWriter"); + + m_TextWriter = textWriter; + } + + ~IcdTextWriter() + { + Dispose(); + } + + public void Dispose() + { + m_TextWriter.Dispose(); + } + } +} diff --git a/ICD.Common/Utils/IStateDisposable.cs b/ICD.Common/Utils/IStateDisposable.cs new file mode 100644 index 0000000..c8bc713 --- /dev/null +++ b/ICD.Common/Utils/IStateDisposable.cs @@ -0,0 +1,15 @@ +using System; + +namespace ICD.Common.Utils +{ + /// + /// Provides features for managing the disposed state of an IDisposable. + /// + public interface IStateDisposable : IDisposable + { + /// + /// Returns true if this instance has been disposed. + /// + bool IsDisposed { get; } + } +} diff --git a/ICD.Common/Utils/IcdConsole.cs b/ICD.Common/Utils/IcdConsole.cs new file mode 100644 index 0000000..0b1d49c --- /dev/null +++ b/ICD.Common/Utils/IcdConsole.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +#if SIMPLSHARP +using Crestron.SimplSharp; +#endif +using ICD.Common.Properties; + +namespace ICD.Common.Utils +{ + public sealed class IcdConsole + { + public enum eAccessLevel + { + Operator = 0, + Programmer = 1, + Administrator = 2 + } + + /// + /// Wraps CrestronConsole.ConsoleCommandResponse for S+ compatibility. + /// + /// + /// + [PublicAPI] + public static void ConsoleCommandResponseLine(string message, params object[] args) + { + ConsoleCommandResponse(message + IcdEnvironment.NewLine, args); + } + + /// + /// Wraps CrestronConsole.ConsoleCommandResponse for S+ compatibility. + /// + /// + /// + [PublicAPI] + public static void ConsoleCommandResponse(string message, params object[] args) + { + message = string.Format(message, args); + +#if SIMPLSHARP + try + { + CrestronConsole.ConsoleCommandResponse(message); + } + catch (NotSupportedException) + { + CrestronConsole.Print(message); + } +#else + System.Console.Write(message, args); +#endif + } + + public static void PrintLine(string message) + { +#if SIMPLSHARP + CrestronConsole.PrintLine(message); +#else + System.Console.WriteLine(message); +#endif + } + + public static void PrintLine(string message, params object[] args) + { +#if SIMPLSHARP + CrestronConsole.PrintLine(message, args); +#else + System.Console.WriteLine(message, args); +#endif + } + + public static void Print(string message) + { +#if SIMPLSHARP + CrestronConsole.Print(message); +#else + System.Console.Write(message); +#endif + } + + public static void Print(string message, params object[] args) + { +#if SIMPLSHARP + CrestronConsole.Print(message, args); +#else + System.Console.Write(message, args); +#endif + } + + public static bool SendControlSystemCommand(string command, ref string result) + { +#if SIMPLSHARP + return CrestronConsole.SendControlSystemCommand(command, ref result); +#else + result = string.Empty; + return false; +#endif + } + + public static void AddNewConsoleCommand(Action callback, string command, string help, eAccessLevel accessLevel) + { +#if SIMPLSHARP + CrestronConsole.AddNewConsoleCommand(str => callback(str), command, help, (ConsoleAccessLevelEnum)(int)accessLevel); +#endif + } + } +} \ No newline at end of file diff --git a/ICD.Common/Utils/IcdEnvironment.SimplSharp.cs b/ICD.Common/Utils/IcdEnvironment.SimplSharp.cs new file mode 100644 index 0000000..44e27d7 --- /dev/null +++ b/ICD.Common/Utils/IcdEnvironment.SimplSharp.cs @@ -0,0 +1,118 @@ +#if SIMPLSHARP +using System; +using System.Collections.Generic; +using Crestron.SimplSharp; +using ICD.Common.Properties; + +namespace ICD.Common.Utils +{ + public static partial class IcdEnvironment + { + public static string NewLine { get { return CrestronEnvironment.NewLine; } } + + public static eRuntimeEnvironment RuntimeEnvironment + { + get { return GetRuntimeEnvironment(CrestronEnvironment.RuntimeEnvironment); } + } + + /// + /// Static constructor. + /// + static IcdEnvironment() + { + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironmentOnProgramStatusEventHandler; + CrestronEnvironment.EthernetEventHandler += CrestronEnvironmentOnEthernetEventHandler; + } + + /// + /// Gets the network address(es) of the processor. + /// + [PublicAPI] + public static IEnumerable NetworkAddresses + { + get + { + const CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET param = + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS; + const EthernetAdapterType type = EthernetAdapterType.EthernetLANAdapter; + short id = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(type); + yield return CrestronEthernetHelper.GetEthernetParameter(param, id); + } + } + + #region Methods + + public static DateTime GetLocalTime() + { + return CrestronEnvironment.GetLocalTime(); + } + + public static eEthernetEventType GetEthernetEventType(Crestron.SimplSharp.eEthernetEventType type) + { + switch (type) + { + case Crestron.SimplSharp.eEthernetEventType.LinkDown: + return eEthernetEventType.LinkDown; + case Crestron.SimplSharp.eEthernetEventType.LinkUp: + return eEthernetEventType.LinkUp; + default: + throw new ArgumentOutOfRangeException("type"); + } + } + + public static eEthernetAdapterType GetEthernetAdapterType(EthernetAdapterType ethernetAdapter) + { + return (eEthernetAdapterType)(int)ethernetAdapter; + } + + public static eProgramStatusEventType GetProgramStatusEventType(Crestron.SimplSharp.eProgramStatusEventType type) + { + switch (type) + { + case Crestron.SimplSharp.eProgramStatusEventType.Stopping: + return eProgramStatusEventType.Stopping; + case Crestron.SimplSharp.eProgramStatusEventType.Paused: + return eProgramStatusEventType.Paused; + case Crestron.SimplSharp.eProgramStatusEventType.Resumed: + return eProgramStatusEventType.Resumed; + default: + throw new ArgumentOutOfRangeException("type"); + } + } + + public static eRuntimeEnvironment GetRuntimeEnvironment(Crestron.SimplSharp.eRuntimeEnvironment runtimeEnvironment) + { + switch (runtimeEnvironment) + { + case Crestron.SimplSharp.eRuntimeEnvironment.SIMPL: + return eRuntimeEnvironment.SimplSharp; + case Crestron.SimplSharp.eRuntimeEnvironment.SimplSharpPro: + return eRuntimeEnvironment.SimplSharpPro; + default: + throw new ArgumentOutOfRangeException("runtimeEnvironment"); + } + } + + #endregion + + #region Private Methods + + private static void CrestronEnvironmentOnEthernetEventHandler(EthernetEventArgs args) + { + EthernetEventCallback handler = OnEthernetEvent; + if (handler != null) + handler(GetEthernetAdapterType(args.EthernetAdapter), GetEthernetEventType(args.EthernetEventType)); + } + + private static void CrestronEnvironmentOnProgramStatusEventHandler(Crestron.SimplSharp.eProgramStatusEventType type) + { + ProgramStatusCallback handler = OnProgramStatusEvent; + if (handler != null) + handler(GetProgramStatusEventType(type)); + } + + #endregion + } +} + +#endif diff --git a/ICD.Common/Utils/IcdEnvironment.Standard.cs b/ICD.Common/Utils/IcdEnvironment.Standard.cs new file mode 100644 index 0000000..db69d5a --- /dev/null +++ b/ICD.Common/Utils/IcdEnvironment.Standard.cs @@ -0,0 +1,34 @@ +#if STANDARD +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace ICD.Common.Utils +{ + public static partial class IcdEnvironment + { + public static string NewLine { get { return Environment.NewLine; } } + + public static DateTime GetLocalTime() + { + return DateTime.Now; + } + + public static eRuntimeEnvironment RuntimeEnvironment { get { return eRuntimeEnvironment.Standard; } } + + public static IEnumerable NetworkAddresses + { + get + { + return NetworkInterface.GetAllNetworkInterfaces() + .Where(ni => ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211 || ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet) + .SelectMany(ni => ni.GetIPProperties().UnicastAddresses + .Where(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork) + .Select(ua => ua.Address.ToString())); + } + } + } +} +#endif diff --git a/ICD.Common/Utils/IcdEnvironment.cs b/ICD.Common/Utils/IcdEnvironment.cs new file mode 100644 index 0000000..afe1f6c --- /dev/null +++ b/ICD.Common/Utils/IcdEnvironment.cs @@ -0,0 +1,57 @@ +using System; + +namespace ICD.Common.Utils +{ + public static partial class IcdEnvironment + { + /// + /// Enumeration to define the various runtime environments a SIMPL# module can run in. + /// + public enum eRuntimeEnvironment + { + SimplSharp, + SimplSharpPro, + Standard + } + + /// + /// Enum for the Program Event Types + /// + public enum eProgramStatusEventType + { + Stopping, + Paused, + Resumed, + } + + /// + /// Enum for the Ethernet Event Types + /// + public enum eEthernetEventType + { + LinkDown, + LinkUp, + } + + /// + /// Enums for the Ethernet Adapter Type + /// + [Flags] + public enum eEthernetAdapterType + { + EthernetUnknownAdapter = 0, + EthernetLanAdapter = 1, + EthernetCsAdapter = 2, + EthernetWifiAdapter = 4, + EthernetLan2Adapter = 8, + } + + public delegate void ProgramStatusCallback(eProgramStatusEventType type); + + public delegate void EthernetEventCallback(eEthernetAdapterType adapter, eEthernetEventType type); + + public static event ProgramStatusCallback OnProgramStatusEvent; + + public static event EthernetEventCallback OnEthernetEvent; + } +} diff --git a/ICD.Common/Utils/IcdErrorLog.cs b/ICD.Common/Utils/IcdErrorLog.cs new file mode 100644 index 0000000..a3cbdcd --- /dev/null +++ b/ICD.Common/Utils/IcdErrorLog.cs @@ -0,0 +1,210 @@ +using System; +using ICD.Common.Properties; + +namespace ICD.Common.Utils +{ + public static class IcdErrorLog + { + private const string CONSOLE_RED = "\x1B[31;1m"; + private const string CONSOLE_GREEN = "\x1B[32;1m"; + private const string CONSOLE_YELLOW = "\x1B[33;1m"; + private const string CONSOLE_BLUE = "\x1B[34;1m"; + //private const string CONSOLE_MAGENTA = "\x1B[35;1m"; + private const string CONSOLE_CYAN = "\x1B[36;1m"; + //private const string CONSOLE_WHITE = "\x1B[37;1m"; + private const string CONSOLE_YELLOW_ON_RED_BACKGROUND = "\x1B[93;41m"; + private const string CONSOLE_RESET = "\x1B[0m"; + + private static readonly SafeCriticalSection s_LoggingSection; + + /// + /// Static constructor. + /// + static IcdErrorLog() + { + s_LoggingSection = new SafeCriticalSection(); + } + + [PublicAPI] + public static void Error(string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_RED); + Crestron.SimplSharp.ErrorLog.Error(message); +#else + System.Console.ForegroundColor = ConsoleColor.Red; + System.Console.Error.WriteLine(message); + System.Console.ResetColor(); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Error(string message, params object[] args) + { + Error(string.Format(message, args)); + } + + [PublicAPI] + public static void Warn(string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_YELLOW); + Crestron.SimplSharp.ErrorLog.Warn(message); +#else + System.Console.ForegroundColor = ConsoleColor.Yellow; + System.Console.Error.WriteLine(message); + System.Console.ResetColor(); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Warn(string message, params object[] args) + { + Warn(string.Format(message, args)); + } + + [PublicAPI] + public static void Notice(string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_BLUE); + Crestron.SimplSharp.ErrorLog.Notice(message); +#else + System.Console.ForegroundColor = ConsoleColor.Blue; + System.Console.Error.WriteLine(message); + System.Console.ResetColor(); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Notice(string message, params object[] args) + { + Notice(string.Format(message, args)); + } + + [PublicAPI] + public static void Ok(string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_GREEN); + Crestron.SimplSharp.ErrorLog.Ok(message); +#else + System.Console.ForegroundColor = ConsoleColor.Green; + System.Console.Error.WriteLine(message); + System.Console.ResetColor(); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Ok(string message, params object[] args) + { + Ok(string.Format(message, args)); + } + + [PublicAPI] + public static void Exception(Exception ex, string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_YELLOW_ON_RED_BACKGROUND); + Crestron.SimplSharp.ErrorLog.Exception(message, ex); +#else + System.Console.ForegroundColor = ConsoleColor.Yellow; + System.Console.BackgroundColor = ConsoleColor.Red; + System.Console.Error.WriteLine("{0}: {1}", ex.GetType().Name, message); + System.Console.ResetColor(); + System.Console.Error.WriteLine(ex.StackTrace); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Exception(Exception ex, string message, params object[] args) + { + message = string.Format(message, args); + Exception(ex, message); + } + + [PublicAPI] + public static void Info(string message) + { + s_LoggingSection.Enter(); + + try + { +#if SIMPLSHARP + message = FormatConsoleColor(message, CONSOLE_CYAN); + Crestron.SimplSharp.ErrorLog.Info(message); +#else + System.Console.ForegroundColor = ConsoleColor.Cyan; + System.Console.Error.WriteLine(message); + System.Console.ResetColor(); +#endif + } + finally + { + s_LoggingSection.Leave(); + } + } + + [PublicAPI] + public static void Info(string message, params object[] args) + { + Info(string.Format(message, args)); + } + + /// + /// Formats the text with the given console color. + /// + /// + /// + /// + private static string FormatConsoleColor(string text, string color) + { + return string.Format("{0}{1}{2}", color, text, CONSOLE_RESET); + } + } +} diff --git a/ICD.Common/Utils/IcdZip.cs b/ICD.Common/Utils/IcdZip.cs new file mode 100644 index 0000000..c75bf12 --- /dev/null +++ b/ICD.Common/Utils/IcdZip.cs @@ -0,0 +1,40 @@ +#if SIMPLSHARP +using Crestron.SimplSharp; + +#else +using System; +using System.IO.Compression; +#endif + +namespace ICD.Common.Utils +{ + /// + /// Utils for managing archives. + /// + public static class IcdZip + { + /// + /// Unzips the archive at the given path. + /// + /// + /// + public static bool Unzip(string path, string outputPath) + { +#if SIMPLSHARP + return CrestronZIP.Unzip(path, outputPath) == CrestronZIP.ResultCode.ZR_OK; +#else + try + { + using (ZipArchive archive = ZipFile.Open(path, ZipArchiveMode.Read)) + archive.ExtractToDirectory(outputPath); + } + catch (Exception) + { + return false; + } + + return true; +#endif + } + } +} diff --git a/ICD.Common/Utils/Json/AbstractGenericJsonConverter.cs b/ICD.Common/Utils/Json/AbstractGenericJsonConverter.cs new file mode 100644 index 0000000..9f3e44a --- /dev/null +++ b/ICD.Common/Utils/Json/AbstractGenericJsonConverter.cs @@ -0,0 +1,78 @@ +using System; +using ICD.Common.Properties; +using Newtonsoft.Json; + +namespace ICD.Common.Utils.Json +{ + public abstract class AbstractGenericJsonConverter : JsonConverter + { + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + WriteJson(writer, (T)value, serializer); + } + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + [PublicAPI] + public abstract void WriteJson(JsonWriter writer, T value, JsonSerializer serializer); + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// + /// The object value. + /// + public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + return ReadJson(reader, (T)existingValue, serializer); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// The existing value of object being read. + /// The calling serializer. + /// + /// The object value. + /// + [PublicAPI] + public abstract T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer); + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(T); + } + } +} diff --git a/ICD.Common/Utils/Json/JsonItemWrapper.cs b/ICD.Common/Utils/Json/JsonItemWrapper.cs new file mode 100644 index 0000000..ec3136e --- /dev/null +++ b/ICD.Common/Utils/Json/JsonItemWrapper.cs @@ -0,0 +1,86 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ICD.Common.Utils.Json +{ + /// + /// Simple wrapper for serialization of an object and its type. + /// + public sealed class JsonItemWrapper + { + private const string TYPE_TOKEN = "t"; + private const string ITEM_TOKEN = "i"; + + private readonly string m_ItemTypeString; + private readonly object m_Item; + + /// + /// Gets the string representation of the item type. Returns null if the item is null. + /// + public string ItemTypeString { get { return m_ItemTypeString; } } + + /// + /// Gets the Type of the item. Returns null if the item is null. + /// + public Type ItemType + { + get { return string.IsNullOrEmpty(m_ItemTypeString) ? null : Type.GetType(m_ItemTypeString); } + } + + /// + /// Gets the wrapped item. + /// + public object Item { get { return string.IsNullOrEmpty(m_ItemTypeString) ? null : m_Item; } } + + /// + /// Constructor. + /// + /// + public JsonItemWrapper(object item) + { + m_ItemTypeString = item == null ? null : item.GetType().FullName; + m_Item = item; + } + + /// + /// Writes the JsonItemWrapper as a JObject. + /// + /// + public void Write(JsonWriter writer) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + writer.WriteStartObject(); + + writer.WritePropertyName(TYPE_TOKEN); + writer.WriteValue(m_ItemTypeString); + + writer.WritePropertyName(ITEM_TOKEN); + writer.WriteValue(JsonConvert.SerializeObject(m_Item)); + + writer.WriteEndObject(); + } + + /// + /// Reads the JToken back to the wrapped object. + /// + /// + /// + public static object ReadToObject(JToken token) + { + if (token == null) + throw new ArgumentNullException("token"); + + string typeString = (string)token.SelectToken(TYPE_TOKEN); + if (string.IsNullOrEmpty(typeString)) + return null; + + string itemString = (string)token.SelectToken(ITEM_TOKEN); + Type type = Type.GetType(typeString); + + return JsonConvert.DeserializeObject(itemString, type); + } + } +} diff --git a/ICD.Common/Utils/Json/JsonUtils.cs b/ICD.Common/Utils/Json/JsonUtils.cs new file mode 100644 index 0000000..fede785 --- /dev/null +++ b/ICD.Common/Utils/Json/JsonUtils.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text; +using ICD.Common.Properties; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils.Json +{ + /// + /// Utility methods for working with JSON. + /// + [PublicAPI] + public static class JsonUtils + { + /// + /// Pretty-prints the JSON document. + /// + /// + [PublicAPI] + public static void Print(string json) + { + int indent = 0; + bool quoted = false; + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < json.Length; i++) + { + char ch = json[i]; + switch (ch) + { + case '{': + case '[': + sb.Append(ch); + if (!quoted) + { + sb.Append(IcdEnvironment.NewLine); + Enumerable.Range(0, ++indent).ForEach(item => sb.Append('\t')); + } + break; + case '}': + case ']': + if (!quoted) + { + sb.Append(IcdEnvironment.NewLine); + Enumerable.Range(0, --indent).ForEach(item => sb.Append('\t')); + } + sb.Append(ch); + break; + case '"': + sb.Append(ch); + bool escaped = false; + int index = i; + while (index > 0 && json[--index] == '\\') + escaped = !escaped; + if (!escaped) + quoted = !quoted; + break; + case ',': + sb.Append(ch); + if (!quoted) + { + sb.Append(IcdEnvironment.NewLine); + Enumerable.Range(0, indent).ForEach(item => sb.Append('\t')); + } + break; + case ':': + sb.Append(ch); + if (!quoted) + sb.Append(" "); + break; + default: + sb.Append(ch); + break; + } + } + + IcdConsole.PrintLine(sb.ToString()); + } + } +} diff --git a/ICD.Common/Utils/MathUtils.cs b/ICD.Common/Utils/MathUtils.cs new file mode 100644 index 0000000..20bb2e8 --- /dev/null +++ b/ICD.Common/Utils/MathUtils.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils +{ + /// + /// Utility methods for math operations. + /// + public static class MathUtils + { + /// + /// Clamps the number between the two values. + /// + /// + /// + /// + /// + public static int Clamp(int number, int min, int max) + { + return (int)Clamp((double)number, min, max); + } + + /// + /// Clamps the number between the two values. + /// + /// + /// + /// + /// + public static ushort Clamp(ushort number, ushort min, ushort max) + { + return (ushort)Clamp((double)number, min, max); + } + + /// + /// Clamps the number between the two values. + /// + /// + /// + /// + /// + public static float Clamp(float number, float min, float max) + { + return (float)Clamp((double)number, min, max); + } + + /// + /// Clamps the number between the two values. + /// + /// + /// + /// + /// + public static double Clamp(double number, double min, double max) + { + return min < max + ? Math.Min(Math.Max(number, min), max) + : Math.Min(Math.Max(number, max), min); + } + + /// + /// Returns the value after the input range has been mapped to a new range + /// + /// Input start. + /// Input end. + /// Output start. + /// Output end. + /// Value. + /// The newly mapped value + public static double MapRange(double inputStart, double inputEnd, double outputStart, double outputEnd, double value) + { + double slope = (outputEnd - outputStart) / (inputEnd - inputStart); + return outputStart + slope * (value - inputStart); + } + + /// + /// Returns the value after the input range has been mapped to a new range + /// + /// Input start. + /// Input end. + /// Output start. + /// Output end. + /// Value. + /// The newly mapped value + public static float MapRange(float inputStart, float inputEnd, float outputStart, float outputEnd, float value) + { + return (float)MapRange((double)inputStart, inputEnd, outputStart, outputEnd, value); + } + + /// + /// Returns the value after the input range has been mapped to a new range + /// + /// Input start. + /// Input end. + /// Output start. + /// Output end. + /// Value. + /// The newly mapped value + public static int MapRange(int inputStart, int inputEnd, int outputStart, int outputEnd, int value) + { + return (int)MapRange((double)inputStart, inputEnd, outputStart, outputEnd, value); + } + + /// + /// Returns the value after the input range has been mapped to a new range + /// + /// Input start. + /// Input end. + /// Output start. + /// Output end. + /// Value. + /// The newly mapped value + public static ushort MapRange(ushort inputStart, ushort inputEnd, ushort outputStart, ushort outputEnd, ushort value) + { + return (ushort)MapRange((double)inputStart, inputEnd, outputStart, outputEnd, value); + } + + /// + /// Maps the date in the given range to the float range 0.0f to 1.0f. + /// 0.5f - The date is half way between the end points. + /// less than 0.0f - the date is before the start. + /// greater than 1.0f - the date is after the end. + /// + /// + /// + /// + /// + public static double MapRange(DateTime start, DateTime end, DateTime value) + { + return MapRange(new TimeSpan(start.Ticks).TotalSeconds, + new TimeSpan(end.Ticks).TotalSeconds, + 0.0f, 1.0f, new TimeSpan(value.Ticks).TotalSeconds); + } + + /// + /// Gets the digit count for the given number. + /// + /// + /// + public static int GetNumberOfDigits(int number) + { + int output = number.ToString().Length; + if (number < 0) + output--; + + return output; + } + + /// + /// Gets the digit count for the given number. + /// + /// + /// + public static int GetNumberOfDigits(uint number) + { + return number.ToString().Length; + } + + /// + /// Takes a sequence of numbers: + /// 1, 3, 5, 6, 7, 8, 9, 10, 12 + /// And calculates the continuous ranges: + /// (1, 1), (3, 3), (5, 10), (12, 12) + /// + public static IEnumerable GetRanges(IEnumerable numbers) + { + if (numbers == null) + throw new ArgumentNullException("numbers"); + + int[] currentRange = null; + + foreach (int number in numbers.Order()) + { + if (currentRange == null) + currentRange = new[] {number, number}; + else if (currentRange[1] == number - 1) + currentRange = new[] {currentRange[0], number}; + else + { + yield return currentRange; + currentRange = new[] {number, number}; + } + } + + if (currentRange != null) + yield return currentRange; + } + + /// + /// Rounds the given number to the nearest item in the given sequence. + /// + /// + /// + /// + public static int RoundToNearest(int number, IEnumerable nearest) + { + if (nearest == null) + throw new ArgumentNullException("nearest"); + + return nearest.Aggregate((x, y) => Math.Abs(x - number) < Math.Abs(y - number) ? x : y); + } + + /// + /// Gets a new, unique id given a sequence of existing ids. + /// + /// + /// + public static int GetNewId(IEnumerable existingIds) + { + if (existingIds == null) + throw new ArgumentNullException("existingIds"); + + IcdHashSet existing = existingIds.ToHashSet(); + return Enumerable.Range(1, int.MaxValue).First(i => !existing.Contains(i)); + } + } +} diff --git a/ICD.Common/Utils/PathUtils.cs b/ICD.Common/Utils/PathUtils.cs new file mode 100644 index 0000000..80a6e6d --- /dev/null +++ b/ICD.Common/Utils/PathUtils.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; +using ICD.Common.Services; +using ICD.Common.Services.Logging; +using ICD.Common.Utils.Extensions; +using ICD.Common.Utils.IO; + +namespace ICD.Common.Utils +{ + /// + /// Provides util methods for working with file/directory paths. + /// + public static class PathUtils + { + #region Properties + + /// + /// Gets the path to the root directory of the processor. + /// + [PublicAPI] + public static string RootPath { get { return IcdDirectory.GetDirectoryRoot("\\"); } } + + /// + /// Gets the path to the NVRAM directory. + /// + [PublicAPI] + public static string NvramPath { get { return Join(RootPath, "NVRAM"); } } + + /// + /// Returns the absolute path to the configuration directory. + /// + /// + [PublicAPI] + public static string ProgramConfigPath + { + get + { + string directoryName = string.Format("Program{0:D2}Config", ProgramUtils.ProgramNumber); + return Join(NvramPath, directoryName); + } + } + + /// + /// Returns the absolute path to the common configuration directory. + /// + [PublicAPI] + public static string CommonConfigPath { get { return Join(NvramPath, "CommonConfig"); } } + + /// + /// Returns the absolute path to the common config library directory. + /// + [PublicAPI] + public static string CommonLibPath { get { return Join(CommonConfigPath, "Lib"); } } + + /// + /// Returns the absolute path to the program config library directory. + /// + [PublicAPI] + public static string ProgramLibPath { get { return Join(ProgramConfigPath, "Lib"); } } + + #endregion + + #region Methods + + /// + /// Creates a path from the given path nodes. + /// + /// + /// + public static string Join(params string[] items) + { + try + { + return items.Skip(1).Aggregate(items.First(), IcdPath.Combine); + } + catch (ArgumentException e) + { + throw new ArgumentException("Failed to join path: " + StringUtils.ArrayFormat(items), e); + } + } + + /// + /// Gets the full path for the given path. + /// + /// + /// + public static string GetFullPath(string path) + { + return Join(IcdDirectory.GetApplicationDirectory(), path); + } + + /// + /// Replaces the filename while leaving the directory and extension intact. + /// + /// + /// + /// + public static string ChangeFilenameWithoutExt(string path, string newName) + { + string dir = IcdPath.GetDirectoryName(path); + string ext = IcdPath.GetExtension(path); + + return Join(dir, newName + ext); + } + + /// + /// Removes the extension from the given path. + /// + /// + /// + public static string GetPathWithoutExtension(string path) + { + string dir = IcdPath.GetDirectoryName(path); + string filename = IcdPath.GetFileNameWithoutExtension(path); + + return Join(dir, filename); + } + + /// + /// Recurses over the file paths at the given directory. + /// + /// + /// + public static IEnumerable RecurseFilePaths(string path) + { + if (!IcdDirectory.Exists(path)) + yield break; + + Queue queue = new Queue(); + queue.Enqueue(path); + + while (queue.Count > 0) + { + path = queue.Dequeue(); + + // Get the subdirectories + try + { + foreach (string subDir in IcdDirectory.GetDirectories(path)) + queue.Enqueue(subDir); + } + catch (Exception e) + { + ServiceProvider.TryGetService().AddEntry(eSeverity.Error, e, e.Message); + } + + // Get the files + string[] files; + try + { + files = IcdDirectory.GetFiles(path); + } + catch (Exception e) + { + ServiceProvider.TryGetService().AddEntry(eSeverity.Error, e, e.Message); + continue; + } + + foreach (string filePath in files) + yield return filePath; + } + } + + /// + /// Searches the program config path, common config path, and application path to + /// find the first config that exists with the given local path. + /// + /// + /// + public static string GetDefaultConfigPath(params string[] localPath) + { + string local = Join(localPath); + + // Program slot configuration + string programPath = Join(ProgramConfigPath, local); + if (PathExists(programPath)) + return programPath; + + // Common program configuration + string commonPath = Join(CommonConfigPath, local); + return PathExists(commonPath) + ? commonPath + : Join(IcdDirectory.GetApplicationDirectory(), local); // Installation defaults + } + + /// + /// Appends the local path to the program config path. + /// + /// + public static string GetProgramConfigPath(params string[] localPath) + { + string local = Join(localPath); + return Join(ProgramConfigPath, local); + } + + /// + /// Searches the application path, program config path and common config path to + /// find the first IR driver that exists with the given local path. + /// + /// + /// + public static string GetIrDriversPath(params string[] localPath) + { + return GetDefaultConfigPath(localPath.Prepend("IRDrivers").ToArray()); + } + + /// + /// Searches the application path, program config path and common config path to + /// find the first SSL Driver that exists with the given local path. + /// + /// + /// + public static string GetSslCertificatesPath(params string[] localPath) + { + return GetDefaultConfigPath(localPath.Prepend("SSLCertificates").ToArray()); + } + + /// + /// Returns true if the given path exists. + /// + /// + /// + public static bool PathExists(string path) + { + return IcdFile.Exists(path) || IcdDirectory.Exists(path); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/PrettyPrint.cs b/ICD.Common/Utils/PrettyPrint.cs new file mode 100644 index 0000000..98d807f --- /dev/null +++ b/ICD.Common/Utils/PrettyPrint.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ICD.Common.Properties; + +namespace ICD.Common.Utils +{ + /// + /// Utils for printing various data types in human readable structures. + /// + public static class PrettyPrint + { + #region Methods + + [PublicAPI] + public static void PrintLine(IDictionary dictionary) + { + if (dictionary == null) + throw new ArgumentNullException("dictionary"); + + IcdConsole.PrintLine(ToString(dictionary)); + } + + [PublicAPI] + public static void PrintLine(IEnumerable sequence) + { + if (sequence == null) + throw new ArgumentNullException("sequence"); + + IcdConsole.PrintLine(ToString(sequence)); + } + + [PublicAPI] + public static string ToString(IDictionary dictionary) + { + if (dictionary == null) + throw new ArgumentNullException("dictionary"); + + StringBuilder builder = new StringBuilder(); + builder.AppendLine("{"); + + foreach (KeyValuePair kvp in dictionary) + { + builder.Append('\t'); + builder.Append(ToString(kvp.Key)); + builder.Append(" : "); + builder.Append(ToString(kvp.Value)); + builder.Append(','); + builder.AppendLine(); + } + + builder.Append("}"); + return builder.ToString(); + } + + [PublicAPI] + public static string ToString(IEnumerable sequence) + { + if (sequence == null) + throw new ArgumentNullException("sequence"); + + StringBuilder builder = new StringBuilder(); + builder.AppendLine("["); + + foreach (T item in sequence) + { + builder.Append('\t'); + builder.Append(ToString(item)); + builder.Append(','); + builder.AppendLine(); + } + + builder.Append("]"); + return builder.ToString(); + } + + [PublicAPI] + public static string ToString(T value) + { +// ReSharper disable once CompareNonConstrainedGenericWithNull + return StringUtils.ToRepresentation(value == null ? null : value.ToString()); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/ProgramUtils.cs b/ICD.Common/Utils/ProgramUtils.cs new file mode 100644 index 0000000..cb97e73 --- /dev/null +++ b/ICD.Common/Utils/ProgramUtils.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if SIMPLSHARP +using Crestron.SimplSharp; +#endif +using ICD.Common.Properties; +using ICD.Common.Services; +using ICD.Common.Services.Logging; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils +{ + public static class ProgramUtils + { + private const string APPLICATION_NAME_KEY = "Application Name"; + private const string APPLICATION_NAME_SIMPL_KEY = "System Name"; + private const string PROGRAM_FILE_KEY = "Program File"; + + private const string COMPILED_ON_KEY = "Compiled On"; + private const string COMPILER_REVISION_KEY = "Compiler Revision"; + private const string COMPILER_REVISION_SIMPL_KEY = "Compiler Rev"; + + private static Dictionary s_ProgComments; + + #region Properties + + /// + /// Lazy-load the prog-comments map. + /// + private static Dictionary ProgComments + { + get { return s_ProgComments ?? (s_ProgComments = ParseProgComments()); } + } + + /// + /// Gets the program number. + /// + [PublicAPI] + public static uint ProgramNumber + { + get + { +#if SIMPLSHARP + return InitialParametersClass.ApplicationNumber; +#else + return 1; +#endif + } + } + + /// + /// Gets the program number in the format XX, eg slot 1 is 01. + /// + [PublicAPI] + public static string ProgramNumberFormatted { get { return string.Format("{0:D2}", ProgramNumber); } } + + /// + /// Gets the compile date of the program. + /// + [PublicAPI] + public static string CompiledDate { get { return ProgComments.GetDefault(COMPILED_ON_KEY, null); } } + + /// + /// Gets the compiler revision version. + /// + [PublicAPI] + public static Version CompilerRevision + { + get + { + string output; + + if (ProgComments.TryGetValue(COMPILER_REVISION_KEY, out output)) + return new Version(output); + + if (ProgComments.TryGetValue(COMPILER_REVISION_SIMPL_KEY, out output)) + return new Version(output); + + return new Version(0, 0); + } + } + + /// + /// Gets the name of the program dll. + /// + [PublicAPI] + public static string ProgramFile { get { return ProgComments.GetDefault(PROGRAM_FILE_KEY, null); } } + + /// + /// Gets the name of the application. + /// + [PublicAPI] + public static string ApplicationName + { + get + { + string output; + + if (ProgComments.TryGetValue(APPLICATION_NAME_KEY, out output)) + return output; + + ProgComments.TryGetValue(APPLICATION_NAME_SIMPL_KEY, out output); + return output; + } + } + + #endregion + + /// + /// Fakes program info, e.g. "Min Firmware Version : 1.009.0029" + /// + /// + /// + [PublicAPI] + public static void PrintProgramInfoLine(string name, object value) + { + name = (name ?? string.Empty).Trim(); + + switch (IcdEnvironment.RuntimeEnvironment) + { + case IcdEnvironment.eRuntimeEnvironment.SimplSharp: + int length = Math.Min(13, name.Length); + name = name.Substring(0, length).PadRight(13); + break; + + case IcdEnvironment.eRuntimeEnvironment.SimplSharpPro: + int proLength = Math.Min(26 - 1, name.Length); + name = name.Substring(0, proLength).PadRight(26); + break; + + case IcdEnvironment.eRuntimeEnvironment.Standard: + name += ' '; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + IcdConsole.PrintLine("{0}: {1}", name, value); + } + + /// + /// Parses the prog comments and pulls program information. + /// + private static Dictionary ParseProgComments() + { + Dictionary output = new Dictionary(); + + try + { + string progInfo = string.Empty; + string command = string.Format("progcomments:{0}", ProgramNumber); + + if (!IcdConsole.SendControlSystemCommand(command, ref progInfo)) + { + ServiceProvider.TryGetService().AddEntry(eSeverity.Warning, "Failed to parse prog comments"); + return output; + } + + foreach (string line in progInfo.Split(new[] {"\n\r", "\r\n", "\n", "\r"})) + { + string[] pair = line.Split(':', 2).ToArray(); + string key = pair[0].Trim(); + string value = pair[1].Trim(); + output[key] = value; + } + } + catch (Exception e) + { + ServiceProvider.TryGetService() + .AddEntry(eSeverity.Error, e, "Failed to parse prog comments - {0}", e.Message); + } + + return output; + } + } +} diff --git a/ICD.Common/Utils/ReflectionUtils.cs b/ICD.Common/Utils/ReflectionUtils.cs new file mode 100644 index 0000000..621b9e1 --- /dev/null +++ b/ICD.Common/Utils/ReflectionUtils.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ICD.Common.Properties; +#if SIMPLSHARP +using Crestron.SimplSharp.Reflection; +using Activator = Crestron.SimplSharp.Reflection.Activator; +#else +using System.IO; +using System.Reflection; +using Microsoft.Extensions.DependencyModel; +using System.Runtime.Loader; +using Activator = System.Activator; +#endif + +namespace ICD.Common.Utils +{ + public static class ReflectionUtils + { + /// + /// Instantiates the given type using the constructor matching the given values. + /// + /// + /// + /// + [PublicAPI] + public static object Instantiate(Type type, params object[] values) + { + if (type == null) + throw new ArgumentNullException("type"); + +#if SIMPLSHARP + CType[] types = values.Select(v => (CType)v.GetType()) + .ToArray(); + ConstructorInfo constructor = ((CType)type).GetConstructor(types); +#else + Type[] types = values.Select(v => v.GetType()) + .ToArray(); + ConstructorInfo constructor = type.GetTypeInfo().GetConstructor(types); +#endif + + if (constructor != null) + return constructor.Invoke(values); + + string message = string.Format("Unable to find constructor for {0}", type.Name); + throw new InvalidOperationException(message); + } + + /// + /// Returns true if the parameters match the method parameters. + /// + /// + /// + /// + public static bool MatchesMethodParameters(MethodBase method, IEnumerable parameters) + { + if (method == null) + throw new ArgumentNullException("method"); + + if (parameters == null) + throw new ArgumentNullException("parameters"); + +#if SIMPLSHARP + CType[] methodTypes +#else + Type[] methodTypes +#endif + = method.GetParameters().Select(p => p.ParameterType).ToArray(); + return ParametersMatchTypes(methodTypes, parameters); + } + + /// + /// Returns true if the if the parameter match the property parameter. + /// + /// + /// + /// + public static bool MatchesPropertyParameter(PropertyInfo property, object parameter) + { + if (property == null) + throw new ArgumentNullException("property"); + +#if SIMPLSHARP + CType propertyType +#else + Type propertyType +#endif + = property.PropertyType; + return ParametersMatchTypes(new[] {propertyType}, new[] {parameter}); + } + + /// + /// Returns true if the parameters array is compatible with the given property types array. + /// + /// + /// + /// +#if SIMPLSHARP + private static bool ParametersMatchTypes(IEnumerable types, IEnumerable parameters) + { + if (types == null) + throw new ArgumentNullException("types"); + + CType[] typesArray = types as CType[] ?? types.ToArray(); +#else + private static bool ParametersMatchTypes(IEnumerable types, IEnumerable parameters) + { + if (types == null) + throw new ArgumentNullException("types"); + + Type[] typesArray = types as Type[] ?? types.ToArray(); +#endif + if (parameters == null) + throw new ArgumentNullException("parameters"); + + object[] parametersArray = parameters as object[] ?? parameters.ToArray(); + + if (parametersArray.Length != typesArray.Length) + return false; + + // Compares each pair of items in the two arrays. + return !parametersArray.Where((t, index) => !ParameterMatchesType(typesArray[index], t)).Any(); + } + + /// + /// Returns true if the parameter can be assigned to the given type. + /// + /// + /// + /// +#if SIMPLSHARP + private static bool ParameterMatchesType(CType type, object parameter) + { + if (type == null) + throw new ArgumentNullException("type"); + + // Can the parameter be assigned a null value? + if (parameter == null) + return (type.IsClass || !type.IsValueType || Nullable.GetUnderlyingType(type) != null); + + return type.IsInstanceOfType(parameter); + } +#else + private static bool ParameterMatchesType(Type type, object parameter) + { + if (type == null) + throw new ArgumentNullException("type"); + + TypeInfo info = type.GetTypeInfo(); + // Can the parameter be assigned a null value? + if (parameter == null) + return (info.IsClass || !info.IsValueType || Nullable.GetUnderlyingType(type) != null); + + return info.IsInstanceOfType(parameter); + } +#endif + + /// + /// Same as doing default(Type). + /// + /// + /// +#if SIMPLSHARP + public static object GetDefaultValue(CType type) +#else + public static object GetDefaultValue(Type type) +#endif + { + if (type == null) + throw new ArgumentNullException("type"); + + return type +#if !SIMPLSHARP + .GetTypeInfo() +#endif + .IsValueType + ? Activator.CreateInstance(type) + : null; + } + + /// + /// Creates an instance of the given type, calling the default constructor. + /// + /// + /// + public static T CreateInstance() + where T : new() + { + return Activator.CreateInstance(); + } + + /// + /// Gets the custom attributes added to the given assembly. + /// + /// + /// + /// + public static IEnumerable GetCustomAttributes(Assembly assembly) + where T : Attribute + { + if (assembly == null) + throw new ArgumentNullException("assembly"); + +#if SIMPLSHARP + return assembly.GetCustomAttributes(typeof(T), false); +#else + return assembly.GetCustomAttributes(); +#endif + } + + /// + /// Loads the assembly at the given path. + /// + /// + /// + public static Assembly LoadAssemblyFromPath(string path) + { +#if SIMPLSHARP + return Assembly.LoadFrom(path); +#else + string fileNameWithOutExtension = Path.GetFileNameWithoutExtension(path); + + bool inCompileLibraries = DependencyContext.Default.CompileLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase)); + bool inRuntimeLibraries = DependencyContext.Default.RuntimeLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase)); + + return inCompileLibraries || inRuntimeLibraries + ? Assembly.Load(new AssemblyName(fileNameWithOutExtension)) + : AssemblyLoadContext.Default.LoadFromAssemblyPath(path); +#endif + } + } +} diff --git a/ICD.Common/Utils/SafeCriticalSection.SimplSharp.cs b/ICD.Common/Utils/SafeCriticalSection.SimplSharp.cs new file mode 100644 index 0000000..7d7603c --- /dev/null +++ b/ICD.Common/Utils/SafeCriticalSection.SimplSharp.cs @@ -0,0 +1,65 @@ +#if SIMPLSHARP +using Crestron.SimplSharp; + +namespace ICD.Common.Utils +{ + /// + /// CCriticalSection tends to get disposed before the parent is done with it. + /// This class is an attempt to gracefully handle the ObjectDisposedExceptions we see on + /// program termination, ocassionally causing the program to restart instead of stop. + /// + public sealed partial class SafeCriticalSection + { + private readonly CCriticalSection m_CriticalSection; + + /// + /// Constructor. + /// + public SafeCriticalSection() + { + m_CriticalSection = new CCriticalSection(); + } + + #region Methods + + /// + /// Block until ownership of the critical section can be obtained. + /// + public void Enter() + { + if (m_CriticalSection == null || m_CriticalSection.Disposed) + return; + + m_CriticalSection.Enter(); + } + + /// + /// Release ownership of the critical section. + /// + public void Leave() + { + if (m_CriticalSection == null || m_CriticalSection.Disposed) + return; + + m_CriticalSection.Leave(); + } + + /// + /// Attempt to enter the critical section without blocking. + /// + /// + /// True, calling thread has ownership of the critical section; otherwise, false. + /// + public bool TryEnter() + { + if (m_CriticalSection == null || m_CriticalSection.Disposed) + return false; + + return m_CriticalSection.TryEnter(); + } + + #endregion + } +} + +#endif diff --git a/ICD.Common/Utils/SafeCriticalSection.Standard.cs b/ICD.Common/Utils/SafeCriticalSection.Standard.cs new file mode 100644 index 0000000..d337d72 --- /dev/null +++ b/ICD.Common/Utils/SafeCriticalSection.Standard.cs @@ -0,0 +1,51 @@ +#if !SIMPLSHARP +using System.Threading; + +namespace ICD.Common.Utils +{ + public sealed partial class SafeCriticalSection + { + private readonly Mutex m_Mutex; + + /// + /// Constructor. + /// + public SafeCriticalSection() + { + m_Mutex = new Mutex(); + } + + #region Methods + + /// + /// Block until ownership of the critical section can be obtained. + /// + public void Enter() + { + m_Mutex.WaitOne(); + } + + /// + /// Release ownership of the critical section. + /// + public void Leave() + { + m_Mutex.ReleaseMutex(); + } + + /// + /// Attempt to enter the critical section without blocking. + /// + /// + /// True, calling thread has ownership of the critical section; otherwise, false. + /// + public bool TryEnter() + { + return m_Mutex.WaitOne(0); + } + + #endregion + } +} + +#endif diff --git a/ICD.Common/Utils/SafeCriticalSection.cs b/ICD.Common/Utils/SafeCriticalSection.cs new file mode 100644 index 0000000..3ffa3a9 --- /dev/null +++ b/ICD.Common/Utils/SafeCriticalSection.cs @@ -0,0 +1,49 @@ +using System; + +namespace ICD.Common.Utils +{ + public sealed partial class SafeCriticalSection + { + /// + /// Enters the critical section, executes the callback and leaves the section. + /// + /// + public void Execute(Action callback) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + try + { + Enter(); + callback(); + } + finally + { + Leave(); + } + } + + /// + /// Enters the critical section, executes the callback and leaves the section. + /// + /// + /// + /// + public T Execute(Func callback) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + try + { + Enter(); + return callback(); + } + finally + { + Leave(); + } + } + } +} diff --git a/ICD.Common/Utils/SafeMutex.cs b/ICD.Common/Utils/SafeMutex.cs new file mode 100644 index 0000000..c2d3bb2 --- /dev/null +++ b/ICD.Common/Utils/SafeMutex.cs @@ -0,0 +1,74 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp; +#else +using System.Threading; +#endif + +namespace ICD.Common.Utils +{ + /// + /// Like the CCriticalSection, the CMutex tends to get disposed before the parent is + /// done with it. This class is an attempt to gracefully handle the ObjectDisposedExceptions + /// we see on program termination, ocassionally causing the program to restart instead of stop. + /// + public sealed class SafeMutex + { +#if SIMPLSHARP + private readonly CMutex m_Mutex; +#else + private readonly Mutex m_Mutex; +#endif + + /// + /// Constructor. + /// + public SafeMutex() + { +#if SIMPLSHARP + m_Mutex = new CMutex(); +#else + m_Mutex = new Mutex(); +#endif + } + + #region Methods + + /// + /// Waits the given number of milliseconds to aquire the mutex. + /// + /// + /// True if the mutex was aquired. + public bool WaitForMutex(int timeout) + { + try + { +#if SIMPLSHARP + return m_Mutex.WaitForMutex(timeout); +#else + return m_Mutex.WaitOne(timeout); +#endif + } + catch (ObjectDisposedException) + { + return false; + } + } + + /// + /// Releases the mutex. + /// + public void ReleaseMutex() + { + try + { + m_Mutex.ReleaseMutex(); + } + catch (ObjectDisposedException) + { + } + } + + #endregion + } +} diff --git a/ICD.Common/Utils/StringUtils.cs b/ICD.Common/Utils/StringUtils.cs new file mode 100644 index 0000000..2fd39b5 --- /dev/null +++ b/ICD.Common/Utils/StringUtils.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ICD.Common.Properties; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils +{ + public static class StringUtils + { + private const ushort ASCII_READABLE_START = 32; + private const ushort ASCII_READABLE_END = 126; + + /// + /// Returns the number as a string in the format "\\x00" + /// + /// + /// + [PublicAPI] + public static string ToHexLiteral(int numeric) + { + return string.Format("\\x{0:X2}", numeric); + } + + /// + /// Returns the character as a string in the format "\\x00" + /// + /// + /// + [PublicAPI] + public static string ToHexLiteral(char c) + { + return ToHexLiteral(Convert.ToInt32(c)); + } + + /// + /// Returns a string as a string in the format "\\x00\\x00..." + /// + /// + /// + [PublicAPI] + public static string ToHexLiteral(string input) + { + string[] strings = input.Select(c => ToHexLiteral(c)).ToArray(); + return string.Join("", strings); + } + + /// + /// Converts a character in the string format "\\x00" to char. + /// + /// + /// + [PublicAPI] + public static char FromHexLiteralCharacter(string character) + { + string hexValue = character.Substring(2); + return (char)Convert.ToByte(hexValue, 16); + } + + /// + /// Converts a string in the format "\\x00\\x00..." to hex representation. + /// + /// + /// + [PublicAPI] + public static string FromHexLiteral(string data) + { + return string.Join("", data.Split(4).Select(s => FromHexLiteralCharacter(s).ToString()).ToArray()); + } + + /// + /// Converts the char to a human readable character, otherwise returns a hex string + /// in the format "\x00" + /// + /// + /// + [PublicAPI] + public static string ToMixedReadableHexLiteral(char c) + { + int numeric = Convert.ToInt32(c); + + if (numeric >= ASCII_READABLE_START && numeric <= ASCII_READABLE_END) + return c.ToString(); + + return ToHexLiteral(c); + } + + /// + /// Converts the input string to a string in the format "\x00\x00" with human + /// readable characters where possible e.g. "Hello World!x\0D" + /// + /// + /// + [PublicAPI] + public static string ToMixedReadableHexLiteral(string input) + { + string[] strings = input.Select(c => ToMixedReadableHexLiteral(c)).ToArray(); + return string.Join("", strings); + } + + /// + /// Converts bytes to an ascii string. + /// + /// + /// + [PublicAPI] + public static string ToString(IEnumerable bytes) + { + byte[] cast = bytes as byte[] ?? bytes.ToArray(); + return Encoding.UTF8.GetString(cast, 0, cast.Length); + } + + /// + /// Converts bytes to an ascii string. + /// + /// + /// + /// + [PublicAPI] + public static string ToString(IEnumerable bytes, int length) + { + byte[] cast = bytes as byte[] ?? bytes.ToArray(); + return Encoding.UTF8.GetString(cast, 0, length); + } + + /// + /// Converts an ascii string to bytes. + /// + /// + /// + [PublicAPI] + public static byte[] ToBytes(string input) + { + return Encoding.UTF8.GetBytes(input); + } + + /// + /// Attempts to parse the string as an integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out int result) + { + return TryConvert(Convert.ToInt32, value, out result); + } + + /// + /// Attempts to parse the string as an unsigned integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out uint result) + { + return TryConvert(Convert.ToUInt32, value, out result); + } + + /// + /// Attempts to parse the string as a float. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out float result) + { + return TryConvert(Convert.ToSingle, value, out result); + } + + /// + /// Attempts to parse the string as a bool. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out bool result) + { + return TryConvert(Convert.ToBoolean, value, out result); + } + + /// + /// Attempts to parse the string via the given conversion function. + /// + /// + /// + /// + /// + private static bool TryConvert(Func convertFunc, string value, out T result) + { + if (convertFunc == null) + throw new ArgumentNullException("convertFunc"); + + result = default(T); + bool retVal = false; + + try + { + result = convertFunc(value); + retVal = true; + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + + return retVal; + } + + /// + /// Returns the object.ToString() with spaces before capital letters. + /// + /// + /// + public static string NiceName(object obj) + { + return NiceName(obj.ToString()); + } + + /// + /// Inserts spaces before capital letters. + /// + /// http://stackoverflow.com/questions/4488969/split-a-string-by-capital-letters + /// + /// + /// + public static string NiceName(string name) + { + Regex regex = new Regex(@" + (?<=[A-Z])(?=[A-Z][a-z]) | + (?<=[^A-Z])(?=[A-Z]) | + (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace); + + return regex.Replace(name, " "); + } + + /// + /// String.Format({0:######}) is unreliable if the number starts with a 0. + /// This method fills an input pattern #### from right to left with characters + /// from the number. + /// + /// + /// + /// + public static string SafeNumericFormat(string phoneFormat, string number) + { + phoneFormat = Reverse(phoneFormat); + number = Reverse(number); + + StringBuilder builder = new StringBuilder(); + + int index = 0; + foreach (char c in phoneFormat) + { + if (index >= number.Length) + { + if (c == '#') + break; + + builder.Append(c); + continue; + } + + if (c == '#') + { + builder.Append(number[index]); + index++; + } + else + builder.Append(c); + } + + return Reverse(builder.ToString()).TrimStart(); + } + + /// + /// Reverses the string. + /// + /// + /// + [PublicAPI] + public static string Reverse(string input) + { + char[] charArray = input.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + + /// + /// Repeats the char the given number of times. + /// + /// + /// + /// + public static string Repeat(char input, int count) + { + return Repeat(input.ToString(), count); + } + + /// + /// Repeats the string the given number of times. + /// + /// + /// + /// + [PublicAPI] + public static string Repeat(string input, int count) + { + return count == 0 ? string.Empty : new StringBuilder().Insert(0, input, count).ToString(); + } + + /// + /// Returns each item.ToString() in the format "[item1, item2, item3...]" + /// + /// + /// + [PublicAPI] + public static string ArrayFormat(IEnumerable items) + { + if (items == null) + throw new ArgumentNullException("items"); + + return string.Format("[{0}]", string.Join(", ", items.Select(i => i.ToString()).ToArray())); + } + + /// + /// Given a sequence of numbers, generates a human readable list of numeric ranges. + /// E.g. [1, 2, 3, 5, 6] becomes "[1-3, 5-6]". + /// + /// + /// + [PublicAPI] + public static string ArrayRangeFormat(IEnumerable items) + { + if (items == null) + throw new ArgumentNullException("items"); + + string[] ranges = MathUtils.GetRanges(items) + .Select(r => r[0] == r[1] + ? r[0].ToString() + : string.Format("{0}-{1}", r[0], r[1])) + .ToArray(); + + return ArrayFormat(ranges); + } + + /// + /// Given a sequence of numbers, generates a human readable list of numeric ranges. + /// E.g. [1, 2, 3, 5, 6] becomes "[1-3, 5-6]". + /// + /// + /// + [PublicAPI] + public static string ArrayRangeFormat(IEnumerable items) + { + if (items == null) + throw new ArgumentNullException("items"); + + return ArrayRangeFormat(items.Select(i => (int)i)); + } + + /// + /// Returns a pair of numbers in the format [a - b] + /// + /// + /// + /// + [PublicAPI] + public static string RangeFormat(object a, object b) + { + return string.Format("[{0} - {1}]", a, b); + } + + /// + /// Capitalizes the first character of the string. + /// + /// + /// + [PublicAPI] + public static string UppercaseFirst(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + return char.ToUpper(input[0]) + input.Substring(1); + } + + /// + /// Formats an IPID to "0xFF" + /// + /// + /// + public static string ToIpIdString(byte ipid) + { + return string.Format("0x{0:X2}", ipid); + } + + /// + /// Formats "0xFF" to an IPID. + /// + /// + /// + public static byte FromIpIdString(string value) + { + value = value.Replace("0x", ""); + return Convert.ToByte(value, 16); + } + + /// + /// Removes all whitespace from the string. + /// + /// + /// + public static string RemoveWhitespace(string text) + { + return text == null ? null : new string(text.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + } + + /// + /// Returns the password as a series of *s + /// + /// + /// + public static string PasswordFormat(string password) + { + return password == null ? null : Repeat('*', password.Length); + } + + /// + /// Pads the given string with quotations for readable type clarity. If the string is null, returns "NULL". + /// + /// + /// + public static string ToRepresentation(string value) + { + return value == null ? "NULL" : string.Format("\"{0}\"", value); + } + + /// + /// Returns the items in the format "x, y, and z" + /// + /// + /// + public static string SerialComma(IEnumerable items) + { + if (items == null) + throw new ArgumentNullException("items"); + + string previous = null; + StringBuilder builder = new StringBuilder(); + + foreach (string item in items) + { + if (previous != null) + builder.AppendFormat("{0}, ", previous); + previous = item; + } + + if (previous != null) + { + if (builder.Length > 0) + builder.AppendFormat("and {0}", previous); + else + builder.Append(previous); + } + + return builder.ToString(); + } + } +} diff --git a/ICD.Common/Utils/TableBuilder.cs b/ICD.Common/Utils/TableBuilder.cs new file mode 100644 index 0000000..2a778bd --- /dev/null +++ b/ICD.Common/Utils/TableBuilder.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICD.Common.Properties; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils +{ + /// + /// TableBuilder provides a way to format a collection of strings to a table. + /// + public sealed class TableBuilder + { + private const char HORIZONTAL = '-'; + private const char VERTICAL = '|'; + + private readonly List m_Rows; + private readonly SafeCriticalSection m_RowsSection; + private readonly string[] m_Columns; + + /// + /// Gets the columns. + /// + [PublicAPI] + public string[] Columns { get { return m_Columns; } } + + /// + /// Gets the number of columns. + /// + [PublicAPI] + public int ColumnsCount { get { return m_Columns.Length; } } + + /// + /// Constructor. + /// + /// + public TableBuilder(params string[] columns) + { + m_Rows = new List(); + m_RowsSection = new SafeCriticalSection(); + m_Columns = columns; + } + + #region Methods + + /// + /// Clears all of the rows. + /// + [PublicAPI] + public void ClearRows() + { + m_RowsSection.Execute(() => m_Rows.Clear()); + } + + /// + /// Calls ToString() for each item and adds the row to the builder. + /// + /// + [PublicAPI] + public void AddRow(params object[] row) + { + string[] stringRow = row.Select(o => string.Format("{0}", o)) + .ToArray(); + AddRow(stringRow); + } + + /// + /// Adds the row to the builder. + /// + /// + [PublicAPI] + public void AddRow(params string[] row) + { + if (row != null && row.Length != m_Columns.Length) + throw new ArgumentException("Row must match columns length."); + + m_RowsSection.Execute(() => m_Rows.Add(row)); + } + + /// + /// Adds an empty row to the builder. + /// + [PublicAPI] + public void AddEmptyRow() + { + AddRow(new string[m_Columns.Length]); + } + + [PublicAPI] + public void AddSeparator() + { + AddRow(null); + } + + [PublicAPI] + public void AddHeader(params string[] row) + { + if (row.Length != m_Columns.Length) + throw new ArgumentException("Row must match columns length."); + + AddSeparator(); + AddRow(row); + AddSeparator(); + } + + /// + /// Gets the output string. + /// + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + m_RowsSection.Enter(); + + try + { + int[] columnWidths = GetColumnWidths(); + + AppendRow(sb, m_Columns, columnWidths); + AppendSeparator(sb, columnWidths); + + foreach (string[] row in m_Rows) + { + if (row == null) + AppendSeparator(sb, columnWidths); + else + AppendRow(sb, row, columnWidths); + } + + AppendSeparator(sb, columnWidths); + } + finally + { + m_RowsSection.Leave(); + } + + return sb.ToString(); + } + + #endregion + + #region Private Methods + + private int[] GetColumnWidths() + { + int[] columnWidths = new int[m_Columns.Length]; + for (int index = 0; index < m_Columns.Length; index++) + columnWidths[index] = GetColumnWidth(index); + + return columnWidths; + } + + private int GetColumnWidth(int index) + { + int titleLength = m_Columns[index].Length + 1; + if (m_Rows.Count == 0) + return titleLength; + + int maxColumnWidth = m_Rows.Except((string[])null) + .Max(x => x[index] != null ? x[index].Length : 0) + 1; + + return (titleLength > maxColumnWidth) ? titleLength : maxColumnWidth; + } + + private static void AppendRow(StringBuilder builder, IList row, IList columnWidths) + { + for (int index = 0; index < row.Count; index++) + { + if (index > 0) + builder.Append(' '); + + string value = row[index] ?? string.Empty; + builder.Append(value.PadRight(columnWidths[index])); + + if (index < row.Count - 1) + builder.Append(VERTICAL); + } + + builder.AppendLine(); + } + + private static void AppendSeparator(StringBuilder sb, ICollection columnWidths) + { + int length = columnWidths.Sum() + (columnWidths.Count - 1) * 2; + string line = new string(HORIZONTAL, length); + + sb.AppendLine(line); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Timers/IcdStopwatch.cs b/ICD.Common/Utils/Timers/IcdStopwatch.cs new file mode 100644 index 0000000..e920508 --- /dev/null +++ b/ICD.Common/Utils/Timers/IcdStopwatch.cs @@ -0,0 +1,67 @@ +#if SIMPLSHARP +using Crestron.SimplSharp; + +#else +using System.Diagnostics; +#endif + +namespace ICD.Common.Utils.Timers +{ + public sealed class IcdStopwatch + { + private readonly Stopwatch m_Stopwatch; + + #region Properties + + public long ElapsedMilliseconds { get { return m_Stopwatch.ElapsedMilliseconds; } } + + public bool IsRunning { get { return m_Stopwatch.IsRunning; } } + + #endregion + + #region Constructors + + /// + /// Constructor. + /// + public IcdStopwatch() + : this(new Stopwatch()) + { + } + + /// + /// Constructor. + /// + /// + public IcdStopwatch(Stopwatch stopwatch) + { + m_Stopwatch = stopwatch; + } + + public static IcdStopwatch StartNew() + { + return new IcdStopwatch(Stopwatch.StartNew()); + } + + #endregion + + #region Methods + + public void Stop() + { + m_Stopwatch.Stop(); + } + + public void Start() + { + m_Stopwatch.Start(); + } + + public void Reset() + { + m_Stopwatch.Reset(); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Timers/IcdTimer.cs b/ICD.Common/Utils/Timers/IcdTimer.cs new file mode 100644 index 0000000..162d62d --- /dev/null +++ b/ICD.Common/Utils/Timers/IcdTimer.cs @@ -0,0 +1,166 @@ +using System; +using ICD.Common.EventArguments; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils.Timers +{ + /// + /// IcdTimer provides events for time increments as well as timer elapsed. + /// + public sealed class IcdTimer : IDisposable + { + private const long DEFAULT_HEARTBEAT_INTERVAL = 500; + + /// + /// Called when the timer is restarted/stopped. + /// + public event EventHandler OnIsRunningChanged; + + /// + /// Called when the timer has elapsed. + /// + public event EventHandler OnElapsed; + + /// + /// Called when the milliseconds count changes. Useful for updating UIs. + /// + public event EventHandler OnMillisecondsChanged; + + private readonly IcdStopwatch m_Stopwatch; + + private readonly SafeTimer m_Heartbeat; + private long m_LastHeartbeatMilliseconds; + + #region Properties + + /// + /// Returns true if the timer is stopped. + /// + public bool IsStopped { get { return !m_Stopwatch.IsRunning; } } + + /// + /// Returns true if the timer is running. + /// + public bool IsRunning { get { return m_Stopwatch.IsRunning; } } + + /// + /// Returns true if the timer has elapsed. + /// + public bool IsElapsed { get { return Milliseconds > Length; } } + + /// + /// Returns the number of milliseconds that have passed since the timer started. + /// + public long Milliseconds { get { return m_Stopwatch.ElapsedMilliseconds; } } + + /// + /// The number of milliseconds before the timer is elapsed. + /// + public long Length { get; private set; } + + /// + /// Gets the remaining number of milliseconds until the timer is elapsed. Returns 0 if the timer has elapsed. + /// + public long Remaining { get { return Math.Max(Length - Milliseconds, 0); } } + + /// + /// Gets the remaining number of seconds until the timer is elapsed. Returns 0 if the timer has elapsed. + /// + public long RemainingSeconds { get { return (long)Math.Ceiling(Remaining / 1000.0f); } } + + #endregion + + #region Constructors + + /// + /// Constructor. + /// + public IcdTimer() + : this(DEFAULT_HEARTBEAT_INTERVAL) + { + } + + /// + /// Creates an IcdTimer with the specified heartbeat interval for the internal timer. + /// This allows a finer resolution for timing than the default 500ms. + /// + /// + public IcdTimer(long heartbeatInterval) + { + m_Heartbeat = new SafeTimer(HeartbeatCallback, heartbeatInterval, heartbeatInterval); + m_Stopwatch = new IcdStopwatch(); + + Stop(); + } + + #endregion + + #region Methods + + /// + /// Release resources. + /// + public void Dispose() + { + OnIsRunningChanged = null; + OnElapsed = null; + OnMillisecondsChanged = null; + + m_Stopwatch.Stop(); + m_Heartbeat.Dispose(); + } + + /// + /// Restarts the timer. + /// + public void Restart(long length) + { + Length = length; + + m_Stopwatch.Reset(); + m_Stopwatch.Start(); + + RaiseOnIsRunningChanged(); + } + + /// + /// Stops the timer. + /// + public void Stop() + { + m_Stopwatch.Stop(); + + RaiseOnIsRunningChanged(); + } + + #endregion + + #region Private Methods + + /// + /// Called when the heartbeat timer elapses. + /// + private void HeartbeatCallback() + { + if (Milliseconds == m_LastHeartbeatMilliseconds) + return; + + OnMillisecondsChanged.Raise(this); + + if (m_LastHeartbeatMilliseconds <= Length && IsElapsed) + OnElapsed.Raise(this); + + m_LastHeartbeatMilliseconds = Milliseconds; + } + + /// + /// Raises the OnIsRunningChanged event. + /// + private void RaiseOnIsRunningChanged() + { + OnIsRunningChanged.Raise(this, new BoolEventArgs(IsRunning)); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Timers/Repeater.cs b/ICD.Common/Utils/Timers/Repeater.cs new file mode 100644 index 0000000..1ad2332 --- /dev/null +++ b/ICD.Common/Utils/Timers/Repeater.cs @@ -0,0 +1,100 @@ +using System; +using ICD.Common.Properties; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils.Timers +{ + /// + /// Simple class for implementing things like volume ramps, where a button has an + /// immediate effect, and then begins ramping after a brief delay. + /// + [PublicAPI] + public sealed class Repeater : IDisposable + { + /// + /// Raised on the initial repeat. + /// + [PublicAPI] + public event EventHandler OnInitialRepeat; + + /// + /// Raised on each subsequent repeat. + /// + [PublicAPI] + public event EventHandler OnRepeat; + + private readonly SafeTimer m_RepeatTimer; + + private readonly long m_BeforeRepeat; + private readonly long m_BetweenRepeat; + + #region Constructor + + /// + /// Constructor. + /// + /// The delay before the second increment + /// The delay between each subsequent repeat + public Repeater(long beforeRepeat, long betweenRepeat) + { + m_RepeatTimer = SafeTimer.Stopped(RepeatCallback); + + m_BeforeRepeat = beforeRepeat; + m_BetweenRepeat = betweenRepeat; + } + + /// + /// Destructor. + /// + ~Repeater() + { + Dispose(); + } + + #endregion + + #region Methods + + /// + /// Release resources. + /// + public void Dispose() + { + m_RepeatTimer.Dispose(); + } + + /// + /// Begin repeating. + /// + [PublicAPI] + public void Start() + { + OnInitialRepeat.Raise(this); + + m_RepeatTimer.Reset(m_BeforeRepeat, m_BetweenRepeat); + } + + /// + /// Stop repeating volume. + /// + [PublicAPI] + public void Stop() + { + m_RepeatTimer.Stop(); + } + + #endregion + + #region Private Methods + + /// + /// Called for every repeat. + /// + private void RepeatCallback() + { + OnRepeat.Raise(this); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Timers/SafeTimer.cs b/ICD.Common/Utils/Timers/SafeTimer.cs new file mode 100644 index 0000000..2477264 --- /dev/null +++ b/ICD.Common/Utils/Timers/SafeTimer.cs @@ -0,0 +1,180 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp; +#else +using System.Threading; +#endif +using ICD.Common.Services; +using ICD.Common.Services.Logging; + +namespace ICD.Common.Utils.Timers +{ + /// + /// SafeTimer wraps CTimer to hide some of the jank. + /// + public sealed class SafeTimer : IStateDisposable + { +#if SIMPLSHARP + private readonly CTimer m_Timer; +#else + private readonly Timer m_Timer; + private int m_DueTime, m_RepeatPeriod; +#endif + private readonly Action m_Callback; + + /// + /// Returns true if this instance has been disposed. + /// + public bool IsDisposed { get; private set; } + + #region Constructors + + /// + /// Creates a timer that is called every repeatPeriod in milliseconds. + /// + /// + /// + public SafeTimer(Action callback, long repeatPeriod) + : this(callback, 0, repeatPeriod) + { + } + + /// + /// Creates a timer that is called in dueTime milliseconds and then every + /// repeatPeriod milliseconds afterwards. + /// + /// + /// + /// + public SafeTimer(Action callback, long dueTime, long repeatPeriod) + { + m_Callback = callback; +#if SIMPLSHARP + m_Timer = new CTimer(SafeCallback, null, dueTime, repeatPeriod); +#else + m_DueTime = (int)dueTime; + m_RepeatPeriod = (int)repeatPeriod; + m_Timer = new Timer(SafeCallback, null, m_DueTime, m_RepeatPeriod); +#endif + } + + /// + /// Creates a timer that is initially stopped. + /// + /// + /// + public static SafeTimer Stopped(Action callback) + { + SafeTimer output = new SafeTimer(callback, 0); + output.Stop(); + return output; + } + + #endregion + + #region Methods + + /// + /// Release resources. + /// + public void Dispose() + { + Stop(); + m_Timer.Dispose(); + IsDisposed = true; + } + + /// + /// Stops the timer. + /// + public void Stop() + { +#if SIMPLSHARP + m_Timer.Stop(); +#else + m_Timer.Change(Timeout.Infinite, Timeout.Infinite); +#endif + } + + /// + /// Immediately calls the callback and resets the timer + /// + public void Trigger() + { +#if SIMPLSHARP + m_Timer.Reset(); +#else + m_Timer.Change(0, m_RepeatPeriod); +#endif + } + + /// + /// Callback is called after the dueTime milliseconds. + /// + /// + public void Reset(long dueTime) + { +#if SIMPLSHARP + m_Timer.Reset(dueTime); +#else + m_DueTime = (int)dueTime; + m_Timer.Change(m_DueTime, m_RepeatPeriod); +#endif + } + + /// + /// Callback is called after the dueTime milliseconds and every repeatPeriod milliseconds. + /// + /// + /// + public void Reset(long dueTime, long repeatPeriod) + { +#if SIMPLSHARP + m_Timer.Reset(dueTime, repeatPeriod); +#else + m_DueTime = (int)dueTime; + m_RepeatPeriod = (int)repeatPeriod; + m_Timer.Change(m_DueTime, m_RepeatPeriod); +#endif + } + + #endregion + + #region Private Methods + + /// + /// Only executes the callback if the timer has not been disposed. + /// Catches any exceptions and logs them. + /// + /// + private void SafeCallback(object unused) + { + // Essentially the meat of this class. There's some weirdness with the garbage collector where + // the reference to the timer will be cleared, and eventually the CTimer will call the callback + // despite being stopped/disposed. + if (m_Timer == null +#if SIMPLSHARP + || m_Timer.Disposed +#endif + ) + return; + + try + { + m_Callback(); + } + catch (Exception e) + { + LogException(e); + } + } + + private void LogException(Exception e) + { + string message = string.Format("{0} failed to execute callback - {1}", GetType().Name, e.Message); + ServiceProvider.TryGetService().AddEntry(eSeverity.Error, e, message); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/TryUtils.cs b/ICD.Common/Utils/TryUtils.cs new file mode 100644 index 0000000..30d1cda --- /dev/null +++ b/ICD.Common/Utils/TryUtils.cs @@ -0,0 +1,56 @@ +using System; + +namespace ICD.Common.Utils +{ + public static class TryUtils + { + /// + /// + /// + /// return type + /// + /// + /// + public static bool Try(Func function, out TResult val) + { + if (function == null) + throw new ArgumentNullException("function"); + + try + { + val = function(); + return true; + } + catch (Exception) + { + val = default(TResult); + return false; + } + } + + public static bool Try(Func function, T1 param1, out TResult val) + { + if (function == null) + throw new ArgumentNullException("function"); + + return Try(() => function(param1), out val); + } + + public static bool Try(Func function, T1 param1, T2 param2, out TResult val) + { + if (function == null) + throw new ArgumentNullException("function"); + + return Try(() => function(param1, param2), out val); + } + + public static bool Try(Func function, T1 param1, T2 param2, T3 param3, + out TResult val) + { + if (function == null) + throw new ArgumentNullException("function"); + + return Try(() => function(param1, param2, param3), out val); + } + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlAttribute.cs b/ICD.Common/Utils/Xml/IcdXmlAttribute.cs new file mode 100644 index 0000000..3f4e91c --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlAttribute.cs @@ -0,0 +1,75 @@ +namespace ICD.Common.Utils.Xml +{ + /// + /// IcdXmlAttribute represents an attribute="value" pair from xml. + /// + public struct IcdXmlAttribute + { + private readonly string m_Name; + private readonly string m_Value; + + public string Name { get { return m_Name; } } + public string Value { get { return m_Value; } } + + /// + /// Constructor. + /// + /// + /// + public IcdXmlAttribute(string name, string value) + { + m_Name = name; + m_Value = value; + } + + /// + /// Implementing default equality. + /// + /// + /// + /// + public static bool operator ==(IcdXmlAttribute a1, IcdXmlAttribute a2) + { + return a1.Equals(a2); + } + + /// + /// Implementing default inequality. + /// + /// + /// + /// + public static bool operator !=(IcdXmlAttribute a1, IcdXmlAttribute a2) + { + return !(a1 == a2); + } + + /// + /// Returns true if this instance is equal to the given object. + /// + /// + /// + public override bool Equals(object other) + { + if (other == null || GetType() != other.GetType()) + return false; + + return GetHashCode() == ((IcdXmlAttribute)other).GetHashCode(); + } + + /// + /// Gets the hashcode for this instance. + /// + /// + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (m_Name == null ? 0 : m_Name.GetHashCode()); + hash = hash * 23 + (m_Value == null ? 0 : m_Value.GetHashCode()); + return hash; + } + } + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlConvert.cs b/ICD.Common/Utils/Xml/IcdXmlConvert.cs new file mode 100644 index 0000000..e9ed1a5 --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlConvert.cs @@ -0,0 +1,101 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronXml; +#else +using System.Xml; +#endif + +namespace ICD.Common.Utils.Xml +{ + public static class IcdXmlConvert + { + public static string ToString(int value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(bool value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(float value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(double value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(decimal value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(long value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(ulong value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(Guid value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(TimeSpan value) + { + return XmlConvert.ToString(value); + } + + public static string ToString(int? value) + { + return value.HasValue ? ToString(value.Value) : null; + } + + public static string ToString(object child) + { + if (child == null) + return null; + + if (child is bool) + return ToString((bool)child); + if (child is byte) + return ToString((byte)child); + if (child is decimal) + return ToString((decimal)child); + if (child is char) + return ToString((char)child); + if (child is double) + return ToString((double)child); + if (child is Guid) + return ToString((Guid)child); + if (child is float) + return ToString((float)child); + if (child is int) + return ToString((int)child); + if (child is long) + return ToString((long)child); + if (child is sbyte) + return ToString((sbyte)child); + if (child is short) + return ToString((short)child); + if (child is TimeSpan) + return ToString((TimeSpan)child); + if (child is uint) + return ToString((uint)child); + if (child is ulong) + return ToString((ulong)child); + if (child is ushort) + return ToString((ushort)child); + + return child.ToString(); + } + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlDocument.cs b/ICD.Common/Utils/Xml/IcdXmlDocument.cs new file mode 100644 index 0000000..cd2973b --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlDocument.cs @@ -0,0 +1,40 @@ +#if SIMPLSHARP +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronXml; + +#else +using System.Xml; +#endif + +namespace ICD.Common.Utils.Xml +{ + public sealed class IcdXmlDocument + { + private readonly XmlDocument m_Document; + + /// + /// Constructor. + /// + public IcdXmlDocument() + { + m_Document = new XmlDocument(); + } + + public void LoadXml(string xml) + { + try + { + m_Document.LoadXml(xml); + } + catch (XmlException e) + { + throw new IcdXmlException(e.Message, e, e.LineNumber, e.LinePosition); + } + } + + public void WriteContentTo(IcdXmlTextWriter writer) + { + m_Document.WriteContentTo(writer.WrappedWriter); + } + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlException.cs b/ICD.Common/Utils/Xml/IcdXmlException.cs new file mode 100644 index 0000000..708228f --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlException.cs @@ -0,0 +1,24 @@ +using System; + +namespace ICD.Common.Utils.Xml +{ + public sealed class IcdXmlException : Exception + { + private int m_LineNumber; + private int m_LinePosition; + + /// + /// Constructor. + /// + /// + /// + /// + /// + public IcdXmlException(string message, Exception inner, int lineNumber, int linePosition) + : base(message, inner) + { + m_LineNumber = lineNumber; + m_LinePosition = linePosition; + } + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlReader.cs b/ICD.Common/Utils/Xml/IcdXmlReader.cs new file mode 100644 index 0000000..4103949 --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlReader.cs @@ -0,0 +1,128 @@ +using System; +#if SIMPLSHARP +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronXml; +#else +using System.Xml; +using System.IO; +#endif + +namespace ICD.Common.Utils.Xml +{ + public sealed class IcdXmlReader : IDisposable + { + private readonly XmlReader m_Reader; + + #region Properties + + public bool HasAttributes { get { return m_Reader.HasAttributes; } } + + public string Name { get { return m_Reader.Name; } } + + public string Value { get { return m_Reader.Value; } } + + public XmlNodeType NodeType { get { return m_Reader.NodeType; } } + + #endregion + + /// + /// Constructor. + /// + /// + public IcdXmlReader(string xml) + { + if (xml == null) + throw new ArgumentNullException("xml"); + +#if SIMPLSHARP + m_Reader = new XmlReader(xml); +#else + m_Reader = XmlReader.Create(new StringReader(xml)); +#endif + } + + ~IcdXmlReader() + { + Dispose(); + } + + #region Methods + + public bool MoveToNextAttribute() + { + return m_Reader.MoveToNextAttribute(); + } + + public void MoveToElement() + { + m_Reader.MoveToElement(); + } + + public string GetAttribute(string name) + { + return m_Reader.GetAttribute(name); + } + + public string ReadString() + { +#if SIMPLSHARP + return m_Reader.ReadString(); +#else + return m_Reader.ReadElementContentAsString(); +#endif + } + + public bool Read() + { + try + { + return m_Reader.Read(); + } + catch (XmlException e) + { + throw new IcdXmlException(e.Message, e, e.LineNumber, e.LinePosition); + } + } + + public void Dispose() + { +#if SIMPLSHARP + m_Reader.Dispose(true); +#else + m_Reader.Dispose(); +#endif + } + + public void Skip() + { + m_Reader.Skip(); + } + + public string ReadElementContentAsString() + { + return m_Reader.ReadElementContentAsString(); + } + + public string ReadOuterXml() + { + return m_Reader.ReadOuterXml(); + } + + public string ReadInnerXml() + { + return m_Reader.ReadInnerXml(); + } + + public long ReadElementContentAsLong() + { + return m_Reader.ReadElementContentAsLong(); + } + + public float ReadElementContentAsFloat() + { + return m_Reader.ReadElementContentAsFloat(); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlTextWriter.SimplSharp.cs b/ICD.Common/Utils/Xml/IcdXmlTextWriter.SimplSharp.cs new file mode 100644 index 0000000..70dba3b --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlTextWriter.SimplSharp.cs @@ -0,0 +1,94 @@ +#if SIMPLSHARP +using System.Text; +using Crestron.SimplSharp.CrestronXml; +using ICD.Common.Utils.IO; + +namespace ICD.Common.Utils.Xml +{ + public sealed partial class IcdXmlTextWriter + { + private readonly XmlTextWriter m_Writer; + + public XmlWriter WrappedWriter { get { return m_Writer; } } + + /// + /// Constructor. + /// + /// + /// + public IcdXmlTextWriter(IcdStream stream, Encoding encoding) + : this(new XmlTextWriter(stream.WrappedStream, encoding)) + { + } + + /// + /// Constructor. + /// + /// + public IcdXmlTextWriter(IcdTextWriter textWriter) + : this(new XmlTextWriter(textWriter.WrappedTextWriter)) + { + } + + /// + /// Constructor. + /// + /// + public IcdXmlTextWriter(XmlTextWriter writer) + { + m_Writer = writer; + m_Writer.Formatting = Crestron.SimplSharp.CrestronXml.Formatting.Indented; + } + + #region Methods + + public void WriteStartElement(string elementName) + { + m_Writer.WriteStartElement(elementName); + } + + public void WriteElementString(string elementName, string value) + { + m_Writer.WriteElementString(elementName, value); + } + + public void WriteEndElement() + { + m_Writer.WriteEndElement(); + } + + public void WriteComment(string comment) + { + m_Writer.WriteComment(comment); + } + + public void Dispose() + { + m_Writer.Dispose(true); + } + + public void WriteAttributeString(string attributeName, string value) + { + m_Writer.WriteAttributeString(attributeName, value); + } + + public void Flush() + { + m_Writer.Flush(); + } + + public void Close() + { + m_Writer.Close(); + } + + public void WriteRaw(string xml) + { + m_Writer.WriteRaw(xml); + } + + #endregion + } +} + +#endif diff --git a/ICD.Common/Utils/Xml/IcdXmlTextWriter.Standard.cs b/ICD.Common/Utils/Xml/IcdXmlTextWriter.Standard.cs new file mode 100644 index 0000000..d085a83 --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlTextWriter.Standard.cs @@ -0,0 +1,102 @@ +using System.Text; +using System.Xml; +using ICD.Common.Utils.IO; + +namespace ICD.Common.Utils.Xml +{ + public sealed partial class IcdXmlTextWriter + { + private readonly XmlWriter m_Writer; + + public XmlWriter WrappedWriter { get { return m_Writer; } } + + /// + /// Constructor. + /// + /// + /// + public IcdXmlTextWriter(IcdStream stream, Encoding encoding) + : this(XmlWriter.Create(stream.WrappedStream, GetSettings(encoding))) + { + } + + /// + /// Constructor. + /// + /// + public IcdXmlTextWriter(IcdTextWriter textWriter) + : this(XmlWriter.Create(textWriter.WrappedTextWriter)) + { + } + + /// + /// Constructor. + /// + /// + public IcdXmlTextWriter(XmlWriter writer) + { + m_Writer = writer; + } + + #region Methods + + public void WriteStartElement(string elementName) + { + m_Writer.WriteStartElement(elementName); + } + + public void WriteElementString(string elementName, string value) + { + m_Writer.WriteElementString(elementName, value); + } + + public void WriteEndElement() + { + m_Writer.WriteEndElement(); + } + + public void WriteComment(string comment) + { + m_Writer.WriteComment(comment); + } + + public void Dispose() + { + m_Writer.Dispose(); + } + + public void WriteAttributeString(string attributeName, string value) + { + m_Writer.WriteAttributeString(attributeName, value); + } + + public void Flush() + { + m_Writer.Flush(); + } + + public void Close() + { + } + + public void WriteRaw(string xml) + { + m_Writer.WriteRaw(xml); + } + + #endregion + + #region Private Methods + + private static XmlWriterSettings GetSettings(Encoding encoding) + { + return new XmlWriterSettings + { + Encoding = encoding, + Indent = true + }; + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Xml/IcdXmlTextWriter.cs b/ICD.Common/Utils/Xml/IcdXmlTextWriter.cs new file mode 100644 index 0000000..4e04573 --- /dev/null +++ b/ICD.Common/Utils/Xml/IcdXmlTextWriter.cs @@ -0,0 +1,8 @@ +using System; + +namespace ICD.Common.Utils.Xml +{ + public sealed partial class IcdXmlTextWriter : IDisposable + { + } +} diff --git a/ICD.Common/Utils/Xml/XmlReaderExtensions.cs b/ICD.Common/Utils/Xml/XmlReaderExtensions.cs new file mode 100644 index 0000000..39fe7c0 --- /dev/null +++ b/ICD.Common/Utils/Xml/XmlReaderExtensions.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronXml; +#else +using System.Xml; +#endif +using ICD.Common.EventArguments; +using ICD.Common.Properties; + +namespace ICD.Common.Utils.Xml +{ + public static class XmlReaderExtensions + { + #region Attributes + + /// + /// Returns true if the attribute exists. + /// + /// + /// + /// + [PublicAPI] + public static bool HasAttribute(this IcdXmlReader extends, string name) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + return extends.GetAttribute(name) != null; + } + + /// + /// Gets the attributes for the current element without moving the reader. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetAttributes(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (!extends.HasAttributes) + return new IcdXmlAttribute[0]; + + List attributes = new List(); + while (extends.MoveToNextAttribute()) + attributes.Add(new IcdXmlAttribute(extends.Name, extends.Value)); + + // Move back to element. + extends.MoveToElement(); + + return attributes.ToArray(); + } + + /// + /// Gets the value of the attribute with the given name. + /// + /// + /// + /// + [PublicAPI] + public static string GetAttributeAsString(this IcdXmlReader extends, string name) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string output = extends.GetAttribute(name); + if (output == null) + throw new FormatException(string.Format("Missing attribute \"{0}\"", name)); + + return output; + } + + /// + /// Gets the value of the attribute with the given name and returns as an integer. + /// + /// + /// + /// + [PublicAPI] + public static int GetAttributeAsInt(this IcdXmlReader extends, string name) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string value = extends.GetAttributeAsString(name); + return int.Parse(value); + } + + /// + /// Gets the value of the attribute with the given name and returns as a bool. + /// + /// + /// + /// + [PublicAPI] + public static bool GetAttributeAsBool(this IcdXmlReader extends, string name) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string value = extends.GetAttributeAsString(name); + return bool.Parse(value); + } + + #endregion + + #region Recurse + + /// + /// Recurses through the entire XML, calling the callback for each Element/Text node. + /// + /// + /// + [PublicAPI] + public static void Recurse(this IcdXmlReader extends, Action callback) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + XmlUtils.Recurse(extends.ReadString(), callback); + } + + #endregion + + #region Skip + + /// + /// Skips over insignificant whitespace. + /// + /// + [PublicAPI] + public static void SkipInsignificantWhitespace(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + while (extends.NodeType == XmlNodeType.Whitespace && extends.Read()) + { + } + } + + /// + /// Skips the current node to the next element. + /// + /// + [PublicAPI] + public static bool SkipToNextElement(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + while (extends.Read() && extends.NodeType != XmlNodeType.Element) + { + } + + return extends.NodeType == XmlNodeType.Element; + } + + #endregion + + #region Get Child Element + + /// + /// Returns true if the current node has child elements. + /// + /// + /// + public static bool HasChildElements(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + while (extends.Read() && extends.NodeType != XmlNodeType.EndElement) + { + if (extends.NodeType == XmlNodeType.Element) + return true; + } + + return false; + } + + /// + /// Gets the child elements for the current element. + /// + /// + /// + public static IEnumerable GetChildElements(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + foreach (IcdXmlReader output in extends.GetChildElementsAsString().Select(child => new IcdXmlReader(child))) + { + output.SkipToNextElement(); + yield return output; + } + } + + /// + /// Gets the child elements for the current element. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetChildElementsAsString(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + // Step into the first child. + extends.SkipToNextElement(); + + while (extends.NodeType == XmlNodeType.Element || extends.NodeType == XmlNodeType.Comment) + { + switch (extends.NodeType) + { + case XmlNodeType.Comment: + extends.Skip(); + break; + + case XmlNodeType.Element: + yield return extends.ReadOuterXml(); + break; + } + + extends.SkipInsignificantWhitespace(); + } + } + + #endregion + + #region Read Element Content + + /// + /// Parses the element content in the format 0xXX as a byte. + /// + /// + /// + [PublicAPI] + public static byte ReadElementContentAsByte(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string content = extends.ReadElementContentAsString(); + return (byte)Convert.ToInt64(content, 16); + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static uint ReadElementContentAsUint(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string content = extends.ReadElementContentAsString(); + return uint.Parse(content); + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static int ReadElementContentAsInt(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string content = extends.ReadElementContentAsString(); + return int.Parse(content); + } + + /// + /// Parses the element content as a ushort. + /// + /// + /// + [PublicAPI] + public static ushort ReadElementContentAsUShort(this IcdXmlReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string content = extends.ReadElementContentAsString(); + return ushort.Parse(content); + } + + /// + /// Parses the element content as an enum. + /// + /// + /// + /// + [PublicAPI] + public static T ReadElementContentAsEnum(this IcdXmlReader extends, bool ignoreCase) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string content = extends.ReadElementContentAsString(); + return EnumUtils.Parse(content, ignoreCase); + } + + #endregion + } +} diff --git a/ICD.Common/Utils/Xml/XmlUtils.cs b/ICD.Common/Utils/Xml/XmlUtils.cs new file mode 100644 index 0000000..4c3f65f --- /dev/null +++ b/ICD.Common/Utils/Xml/XmlUtils.cs @@ -0,0 +1,1013 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICD.Common.EventArguments; +using ICD.Common.Properties; +using ICD.Common.Utils.IO; + +namespace ICD.Common.Utils.Xml +{ + /// + /// XmlUtils provides utility methods for working with XML. + /// + public static class XmlUtils + { + /// + /// Returns the contents of the outermost element as a string. + /// + /// + /// + [PublicAPI] + public static string GetInnerXml(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.ReadInnerXml(); + } + } + + #region Attributes + + /// + /// Returns true if the attribute exists. + /// + /// + /// + /// + [PublicAPI] + public static bool HasAttribute(string xml, string name) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + return reader.HasAttribute(name); + } + + /// + /// Gets the attributes for the current xml element. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetAttributes(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.GetAttributes(); + } + } + + /// + /// Convenience method for getting attribute by name. + /// + /// + /// + /// + [PublicAPI] + public static IcdXmlAttribute GetAttribute(string xml, string name) + { + return GetAttributes(xml).First(a => a.Name == name); + } + + /// + /// Gets the value of the attribute with the given name. + /// + /// + /// + /// + [PublicAPI] + public static string GetAttributeAsString(string xml, string name) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.GetAttributeAsString(name); + } + } + + /// + /// Gets the value of the attribute with the given name and returns as an integer. + /// + /// + /// + /// + [PublicAPI] + public static int GetAttributeAsInt(string xml, string name) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.GetAttributeAsInt(name); + } + } + + /// + /// Gets the value of the attribute with the given name and returns as a bool. + /// + /// + /// + /// + [PublicAPI] + public static bool GetAttributeAsBool(string xml, string name) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.GetAttributeAsBool(name); + } + } + + #endregion + + #region Recurse + + /// + /// Recurses through the entire XML, calling the callback for each Element/Text node. + /// + /// + /// + [PublicAPI] + public static void Recurse(string xml, Action callback) + { + Recurse(xml, new Stack(), callback); + } + + /// + /// Recurses through the entire XML, calling the callback for each Element/Text node. + /// + /// + /// + /// + private static void Recurse(string xml, Stack path, Action callback) + { + IcdXmlReader childReader; + + try + { + childReader = new IcdXmlReader(xml); + childReader.SkipToNextElement(); + } + catch (IcdXmlException) + { + return; + } + + path.Push(childReader.Name); + string[] pathOutput = path.Reverse().ToArray(); + + callback(new XmlRecursionEventArgs(xml, pathOutput)); + + foreach (string child in childReader.GetChildElementsAsString()) + Recurse(child, path, callback); + + path.Pop(); + childReader.Dispose(); + } + + #endregion + + #region Get Child Element + + /// + /// Returns true if the given xml element contains at least 1 child element. + /// + /// + /// + public static bool HasChildElements(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.HasChildElements(); + } + } + + /// + /// Gets the child elements from the topmost element in the xml. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetChildElements(string xml) + { + IcdXmlReader reader = new IcdXmlReader(xml); + reader.SkipToNextElement(); + + foreach (IcdXmlReader child in reader.GetChildElements()) + yield return child; + + reader.Dispose(); + } + + /// + /// Returns the child elements with the given name. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetChildElementsAsString(string xml, string element) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + + foreach (IcdXmlReader child in reader.GetChildElements()) + { + string output = null; + if (child.Name == element) + output = child.ReadOuterXml(); + + child.Dispose(); + + if (output != null) + yield return output; + } + } + } + + /// + /// Gets the child elements for the current element. + /// + /// + /// + [PublicAPI] + public static IEnumerable GetChildElementsAsString(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + foreach (string item in reader.GetChildElementsAsString()) + yield return item; + } + } + + /// + /// Gets the immediate child element with the given name. + /// + /// + /// + /// + [PublicAPI] + public static string GetChildElementAsString(string xml, string element) + { + string output; + + if (!TryGetChildElementAsString(xml, element, out output)) + throw new FormatException(string.Format("No child element named {0}", element)); + return output; + } + + /// + /// Gets the immediate child element with the given name. + /// + /// + /// + /// + /// Whether or not the method succeeded + [PublicAPI] + public static bool TryGetChildElementAsString(string xml, string element, out string output) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + + foreach (IcdXmlReader child in reader.GetChildElements()) + { + output = null; + if (child.Name == element) + output = child.ReadOuterXml(); + + child.Dispose(); + + if (output != null) + return true; + } + } + output = null; + return false; + } + + #endregion + + #region Read Child Element + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static string ReadChildElementContentAsString(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsString(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static int ReadChildElementContentAsInt(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsInt(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static uint ReadChildElementContentAsUint(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsUint(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static long ReadChildElementContentAsLong(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsLong(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static ushort ReadChildElementContentAsUShort(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsUShort(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + private static float? ReadChildElementContentAsFloat(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsFloat(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static bool ReadChildElementContentAsBoolean(string xml, string childElement) + { + // IcdXmlReader.ReadElementContentAsBoolean() is too case sensitive + string output = ReadChildElementContentAsString(xml, childElement); + return bool.Parse(output); + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + [PublicAPI] + public static byte ReadChildElementContentAsByte(string xml, string childElement) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsByte(); + } + } + + /// + /// Gets the content of an immediate child. + /// + /// + /// + /// + /// + [PublicAPI] + public static T ReadChildElementContentAsEnum(string xml, string childElement, bool ignoreCase) + { + string child = GetChildElementAsString(xml, childElement); + using (IcdXmlReader reader = new IcdXmlReader(child)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsEnum(ignoreCase); + } + } + + #endregion + + #region Try Read Child Element + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static string TryReadChildElementContentAsString(string xml, string childElement) + { + try + { + return ReadChildElementContentAsString(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static int? TryReadChildElementContentAsInt(string xml, string childElement) + { + try + { + return ReadChildElementContentAsInt(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static long? TryReadChildElementContentAsLong(string xml, string childElement) + { + try + { + return ReadChildElementContentAsLong(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static ushort? TryReadChildElementContentAsUShort(string xml, string childElement) + { + try + { + return ReadChildElementContentAsUShort(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static float? TryReadChildElementContentAsFloat(string xml, string childElement) + { + try + { + return ReadChildElementContentAsFloat(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of an immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static bool? TryReadChildElementContentAsBoolean(string xml, string childElement) + { + try + { + return ReadChildElementContentAsBoolean(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns null if the child element was not found. + /// + /// + /// + /// + [PublicAPI] + public static byte? TryReadChildElementContentAsByte(string xml, string childElement) + { + try + { + return ReadChildElementContentAsByte(xml, childElement); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Gets the content of the immediate child. Returns default if the child element could not be parsed. + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static T? TryReadChildElementContentAsEnum(string xml, string childElement, bool ignoreCase) + where T : struct + { + T? output; + return !TryReadChildElementContentAsEnum(xml, childElement, ignoreCase, out output) ? null : output; + } + + /// + /// Gets the content of the immediate child. Returns false if the child element could not be parsed. + /// + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static bool TryReadChildElementContentAsEnum(string xml, string childElement, bool ignoreCase, out T output) + { + output = default(T); + + try + { + output = ReadChildElementContentAsEnum(xml, childElement, ignoreCase); + return true; + } + // Null xml + catch (ArgumentException) + { + } + catch (FormatException) + { + } + + return false; + } + + #endregion + + #region Read Element Content + + /// + /// Returns the content for a single element. + /// + /// + /// + [PublicAPI] + public static string ReadElementContent(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.SkipToNextElement(); + return reader.ReadElementContentAsString(); + } + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static uint ReadElementContentAsUint(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.Read(); + return reader.ReadElementContentAsUint(); + } + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static int ReadElementContentAsInt(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.Read(); + return reader.ReadElementContentAsInt(); + } + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static ushort ReadElementContentAsUShort(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.Read(); + return reader.ReadElementContentAsUShort(); + } + } + + /// + /// Parses the element content as an enum. + /// + /// + /// + /// + /// + [PublicAPI] + public static T ReadElementContentAsEnum(string xml, bool ignoreCase) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + reader.Read(); + return reader.ReadElementContentAsEnum(ignoreCase); + } + } + + #endregion + + #region Try Read Element Content + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static uint? TryReadElementContentAsUint(string xml) + { + IcdXmlReader reader = new IcdXmlReader(xml); + reader.Read(); + try + { + return reader.ReadElementContentAsUint(); + } + catch + { + return null; + } + } + + /// + /// Parses the element content as a uint. + /// + /// + /// + [PublicAPI] + public static int? TryReadElementContentAsInt(string xml) + { + IcdXmlReader reader = new IcdXmlReader(xml); + reader.Read(); + try + { + return reader.ReadElementContentAsInt(); + } + catch + { + return null; + } + } + + #endregion + + /// + /// Returns the name of the first element in the given xml. + /// + /// + /// + /// No element in the xml + [PublicAPI] + public static string ReadElementName(string xml) + { + using (IcdXmlReader reader = new IcdXmlReader(xml)) + { + if (!reader.SkipToNextElement()) + throw new FormatException("Unable to read element name, no element in given xml"); + return reader.Name; + } + } + + #region Read Content + + /// + /// Calls readKey and readValue for each key/value pair in the dict. + /// + /// + /// + /// + /// + /// + /// + /// + public static IEnumerable> ReadDictFromXml( + string xml, string rootElement, string childElement, string keyElement, string valueElement, + Func readKey, Func readValue) + { + if (readKey == null) + throw new ArgumentNullException("readKey"); + + if (readValue == null) + throw new ArgumentNullException("readValue"); + + Func> readChild = + child => + { + string key; + string value; + + TryGetChildElementAsString(child, keyElement, out key); + TryGetChildElementAsString(child, valueElement, out value); + + return new KeyValuePair(readKey(key), readValue(value)); + }; + + return ReadListFromXml(xml, rootElement, childElement, readChild); + } + + /// + /// Calls childElementCallback for each item in the list. + /// + /// + /// + /// + /// + public static IEnumerable ReadListFromXml(string xml, string rootElement, string childElement, + Func readChild) + { + if (readChild == null) + throw new ArgumentNullException("readChild"); + + if (!TryGetChildElementAsString(xml, rootElement, out xml)) + yield break; + + foreach (string child in GetChildElementsAsString(xml, childElement)) + yield return readChild(child); + } + + #endregion + + #region Write Content + + /// + /// Serializes the given list to XML using IcdXmlConvert to write each key and value. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static void WriteDictToXml(IcdXmlTextWriter writer, IDictionary dict, + string rootElement, string childElement, string keyElement, + string valueElement) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + if (dict == null) + throw new ArgumentNullException("dict"); + + Action writeKey = key => writer.WriteElementString(keyElement, IcdXmlConvert.ToString(key)); + Action writeValue = value => writer.WriteElementString(valueElement, IcdXmlConvert.ToString(value)); + + WriteDictToXml(writer, dict, rootElement, childElement, writeKey, writeValue); + } + + /// + /// Serializes the given list to XML using the actions to write each item. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static void WriteDictToXml(IcdXmlTextWriter writer, IDictionary dict, + string rootElement, string childElement, Action writeKey, + Action writeValue) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + if (dict == null) + throw new ArgumentNullException("dict"); + + if (writeKey == null) + throw new ArgumentNullException("writeKey"); + + if (writeValue == null) + throw new ArgumentNullException("writeValue"); + + Action> writeItem = + pair => + { + writer.WriteStartElement(childElement); + { + writeKey(pair.Key); + writeValue(pair.Value); + } + writer.WriteEndElement(); + }; + + WriteListToXml(writer, dict, rootElement, writeItem); + } + + /// + /// Serializes the given list to XML using IcdXmlConvert to write each item. + /// + /// + /// + /// + /// + /// + public static void WriteListToXml(IcdXmlTextWriter writer, IEnumerable list, string listElement, + string childElement) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + if (list == null) + throw new ArgumentNullException("list"); + + Action writeItem = child => writer.WriteElementString(childElement, IcdXmlConvert.ToString(child)); + WriteListToXml(writer, list, listElement, writeItem); + } + + /// + /// Serializes the given list to XML using the writeItem action to write each item. + /// + /// + /// + /// + /// + /// + public static void WriteListToXml(IcdXmlTextWriter writer, IEnumerable list, string listElement, + Action writeItem) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + if (list == null) + throw new ArgumentNullException("list"); + + if (writeItem == null) + throw new ArgumentNullException("writeItem"); + + writer.WriteStartElement(listElement); + { + foreach (T child in list) + writeItem(child); + } + writer.WriteEndElement(); + } + + #endregion + + /// + /// Returns true if the given xml is valid. + /// + /// + /// + [PublicAPI] + public static bool IsValidXml(string xml) + { + try + { + IcdXmlDocument document = new IcdXmlDocument(); + document.LoadXml(xml); + return true; + } + catch (IcdXmlException) + { + return false; + } + } + + /// + /// Prints the xml document. + /// + /// + [PublicAPI] + public static void Print(string xml) + { + string result = Format(xml); + IcdConsole.PrintLine(result); + } + + /// + /// Formats the given xml string into a human readable structure with indentations. + /// + /// + /// + [PublicAPI] + public static string Format(string xml) + { + using (IcdMemoryStream mStream = new IcdMemoryStream()) + { + using (IcdXmlTextWriter writer = new IcdXmlTextWriter(mStream, Encoding.UTF8)) + { + IcdXmlDocument document = new IcdXmlDocument(); + + // Load the XmlDocument with the XML. + document.LoadXml(xml); + + // Write the XML into a formatting IcdXmlTextWriter + document.WriteContentTo(writer); + writer.Flush(); + mStream.Flush(); + + // Have to rewind the MemoryStream in order to read its contents. + mStream.Position = 0; + return new IcdStreamReader(mStream).ReadToEnd(); + } + } + } + } +}