// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using OpenTap.Translation;
//**** WARNING ****//
// This file is used in many projects(link existing), but only with internal protection.
// NEVER insert a public class here or things will break due to multiple definitions of the same class.
// Bugs introduced here will cause bugs in other projects too, so be careful.
// **
namespace OpenTap
{
///
/// Class to ease the use of reflection.
///
internal static class ReflectionHelper
{
/// Returns true if 'type' and 'basetype' are equal.
///
///
///
public static bool IsA(this ITypeData type, Type basetype)
{
if (type is TypeData cst)
return cst.Type == basetype;
return false;
}
public static T GetBaseType(this ITypeData type) where T: ITypeData
{
var typeIterator = type;
while (typeIterator != null)
{
if (typeIterator is TypeData)
break;
if (typeIterator is T t2)
return t2;
typeIterator = typeIterator.BaseType;
}
return default;
}
/// Really fast direct descendant test. This checks for reference equality of the type or a base type, and 'baseType'.
/// Given these constraints are met, this can be 6x faster than DescendsTo, but should only be used in special cases.
public static bool DirectInheritsFrom(this ITypeData type, ITypeData baseType)
{
do
{
if(ReferenceEquals(type, baseType)) return true;
type = type.BaseType;
} while (type != null);
return false;
}
public static TypeData AsTypeData(this ITypeData type)
{
do
{
if (type is TypeData td)
return td;
type = type?.BaseType;
} while (type != null);
return null;
}
static Dictionary displayLookup = new Dictionary(1024);
static object displayLookupLock = new object();
public static DisplayAttribute GetDisplayAttribute(this MemberInfo type)
{
lock (displayLookupLock)
{
if (!displayLookup.ContainsKey(type))
{
DisplayAttribute attr;
try
{
attr = type.GetAttribute();
}
catch
{ // This might happen for outdated plugins where an Attribute type ceased to exist.
attr = null;
}
if (attr == null)
{
attr = new DisplayAttribute(type.Name, null, Order: -10000, Collapsed: false);
}
displayLookup[type] = attr;
}
return displayLookup[type];
}
}
///
/// Parses a DisplayName into a group:name pair.
///
///
///
///
internal static string ParseDisplayname(string displayName, out string group)
{
group = "";
var parts = displayName.Trim().TrimStart('-').Split('\\');
if (parts.Length == 1) return displayName;
else if (parts.Length >= 2)
{
group = parts[0].Trim();
return parts.Last().Trim();
}
else
return displayName.Trim();
}
static object[] getAttributes(MemberInfo mem)
{
try
{
return mem.GetCustomAttributes(true);
}
catch
{
return Array.Empty();
}
}
static readonly ConditionalWeakTable attrslookup = new ConditionalWeakTable();
public static object[] GetAllCustomAttributes(this MemberInfo prop)
{
return attrslookup.GetValue(prop, getAttributes);
}
static object[] getAttributesNoInherit(MemberInfo mem)
{
try
{
return mem.GetCustomAttributes(false);
}
catch
{
return Array.Empty();
}
}
static readonly ConditionalWeakTable attrslookupNoInherit = new ConditionalWeakTable();
public static object[] GetAllCustomAttributes(this MemberInfo prop, bool inherit)
{
if(!inherit)
return attrslookupNoInherit.GetValue(prop, getAttributesNoInherit);
return GetAllCustomAttributes(prop);
}
///
/// Gets the custom attributes. Both type and property attributes. Also inherited attributes.
///
///
///
///
public static T[] GetCustomAttributes(this MemberInfo prop) where T : Attribute
{
// This method impacts GUI, serialization and even test plan execution times.
// it needs to be as fast as possible.
// Avoid allocation when there is nothing of type T in the attributes
var array = GetAllCustomAttributes(prop);
int cnt = 0;
foreach (var attr in array)
{
if (attr is T)
cnt++;
}
if (cnt == 0)
return Array.Empty(); // This avoids allocation of empty arrays.
T[] result = new T[cnt];
cnt = 0;
foreach (var attr in array)
{
if (attr is T a)
{
result[cnt] = a;
cnt++;
}
}
return result;
}
///
/// Gets the first or default of the custom attributes for this member. Both type and property attributes also inherited attributes.
///
///
///
///
public static T GetFirstOrDefaultCustomAttribute(this MemberInfo prop) where T : Attribute
{
foreach (var attr in GetAllCustomAttributes(prop))
if (attr is T a)
return a;
return null;
}
///
/// Gets the first or default of the custom attributes for this property. Both type and property attributes also inherited attributes.
///
///
///
///
public static T GetAttribute(this MemberInfo prop) where T : Attribute
{
return GetFirstOrDefaultCustomAttribute(prop);
}
///
/// return whether the property has a given attribute T.
///
///
///
///
public static bool HasAttribute(this MemberInfo prop) where T : Attribute
{
return prop.IsDefined(typeof(T), true);
}
///
/// Return whether the attribute has the given attribute T.
///
///
///
///
public static bool HasAttribute(this Type t) where T : Attribute
{
return t.IsDefined(typeof(T), true);
}
///
/// Returns true if a MemberInfo is Browsable.
///
///
///
public static bool IsBrowsable(this MemberInfo m)
{
var b = m.GetAttribute();
if (b == null) return true;
return b.Browsable;
}
///
/// Check whether a type 'descends' to otherType or "can be otherType".
///
///
///
///
public static bool DescendsTo(this Type t, Type otherType)
{
if (t == otherType)
return true;
if (otherType.IsGenericTypeDefinition)
{ // In the case otherType is constructed from typeof(X<>), not typeof(X).
if (otherType.IsInterface)
{
var interfaces = t.GetInterfaces();
foreach (var iface in interfaces)
{
if (iface.IsGenericType)
{
if (iface.GetGenericTypeDefinition() == otherType)
return true;
}
}
if (t.IsInterface)
{
if (t.IsGenericType)
{
if (t.GetGenericTypeDefinition() == otherType)
return true;
}
}
}
else
{
Type super = t;
while (super != typeof(object) && super != null /*if not a class*/)
{
if (super.IsGenericType && super.GetGenericTypeDefinition() == otherType)
return true;
super = super.BaseType;
}
}
}
return otherType.IsAssignableFrom(t);
}
///
/// returns whether t has a given interface T.
///
///
///
///
public static bool HasInterface(this Type t)
{
return typeof(T).IsAssignableFrom(t);
}
///
/// Returns true if a type is numeric.
///
public static bool IsNumeric(this Type t)
{
if (t.IsEnum)
return false;
switch (Type.GetTypeCode(t))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
/// Returns true if a type is numeric.
public static bool IsNumeric(this ITypeData t)
{
return t.AsTypeData()?.Type.IsNumeric() == true;
}
/// Returns true if a type is a C# primitive.
public static bool IsPrimitive(this ITypeData t)
{
return t.AsTypeData()?.Type.IsPrimitive ?? false;
}
/// Creates an instance of t with no constructor arguments.
public static object CreateInstance(this Type t, params object[] args) => Activator.CreateInstance(t, args);
/// Creates an instance of type t. If an error occurs it returns null and prints an error message.
public static object CreateInstanceSafe(this ITypeData t, params object[] args)
{
try
{
return t.CreateInstance(args);
}
catch(Exception e)
{
var log = Log.CreateSource("Reflection");
log.Error($"Cannot create instance of {t.Name}: '{e.Message}'");
log.Debug(e);
}
return null;
}
///
/// If Type is a collection of items, get the element type.
///
///
///
static public Type GetEnumerableElementType(this Type enumType)
{
if (enumType.IsArray)
return enumType.GetElementType();
if (enumType.IsGenericTypeDefinition)
return null;
try
{
// check if it *has* the interface
var ienumInterface = enumType.GetInterface(typeof(IEnumerable<>).Name);
if (ienumInterface != null)
return ienumInterface.GetGenericArguments().FirstOrDefault();
}
catch (AmbiguousMatchException)
{
// the type implements multiple different IEnumerable<> interfaces.
// this is an odd case that is not commonly seen.
}
// check if it *is* the interface.
if(enumType.IsInterface && enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return enumType.GetGenericArguments().FirstOrDefault();
}
return null;
}
static readonly Dictionary propslookup = new Dictionary(1024);
static PropertyInfo[] getPropertiesTap(Type t)
{
lock (propslookup)
{
if (propslookup.ContainsKey(t) == false)
{
try
{
propslookup[t] = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
}
catch
{
propslookup[t] = Array.Empty();
}
}
return propslookup[t];
}
}
public static bool ContainsMember(this IParameterMemberData p, (object Source, IMemberData Member) item)
{
return p.ParameterizedMembers.Contains(item);
}
/// Extracts properties from a Type that are public and not static. Default GetProperties() also returns static properties.
public static PropertyInfo[] GetPropertiesTap(this Type type)
{
return getPropertiesTap(type);
}
static readonly Dictionary propslookup2 = new Dictionary(1024);
static MethodInfo[] getMethodsTap(Type t)
{
lock (propslookup2)
{
if (propslookup2.ContainsKey(t) == false)
{
try
{
propslookup2[t] = t.GetMethods();
}
catch
{
propslookup2[t] = Array.Empty();
}
}
return propslookup2[t];
}
}
/// Extracts properties from a Type that are public and not static. Default GetProperties() also returns static properties.
public static MethodInfo[] GetMethodsTap(this Type type)
{
return getMethodsTap(type);
}
public static bool MethodOverridden(this Type t, Type baseType, string methodName)
{
var m1 = baseType.GetMethod(methodName).MethodHandle.Value;
var m2 = t.GetMethod(methodName).MethodHandle.Value;
return m1 != m2;
}
/// Get the base C# type of a given type.
internal static T As(this ITypeData type) where T: ITypeData
{
for(;type != null; type = type.BaseType)
if (type is T td)
return td;
return default;
}
public static void GetAttributes(this IReflectionData mem, System.Collections.IList outList)
{
foreach (var item in mem.Attributes)
{
if (item is T x)
outList.Add(x);
}
}
}
internal class Memorizer
{
///
/// Enumerates how cyclic invokes can be handled.
///
public enum CyclicInvokeMode
{
///
/// Specifies that an exception should be thrown.
///
ThrowException,
///
/// Specifies that default(ResultT) should be returned.
///
ReturnDefaultValue
}
}
internal interface IMemorizer
{
ResultT Invoke(ArgT arg);
void InvalidateAll();
}
///
/// Convenient when some memorizer optimizations can be done.
/// Includes functionality for decay time and max number of elements.
/// It assumes that the same ArgT will always result in the same ResultT.
///
///
///
///
internal class Memorizer : Memorizer, IMemorizer
{
///
/// Used for locking the invokation of a specific MemorizerKey.
/// This makes it possible to call Invoke in parallel and avoid recalculating the same value multiple times.
///
class LockObject
{
public bool IsLocked;
}
/// If a certain time passes a result should be removed. By default, never.
public TimeSpan SoftSizeDecayTime = TimeSpan.MaxValue;
protected Func getKey;
protected Func getData = argt => (ResultT)(object)argt;
readonly Dictionary lastUse = new Dictionary();
readonly Dictionary memorizerTable = new Dictionary();
readonly Dictionary locks = new Dictionary();
/// Can be used to create a validation key for each key in the memorizer.
/// Validation keys are used for checking if the memory is up to date or if it should be refreshed.
public Func Validator { get; set; } = null;
readonly Dictionary validatorData = new Dictionary();
///
/// Specifies how to handle situations where an Invoke(x) triggers another Invoke(x) in the same thread.
/// Since this might cause infinite recursion, it is not allowed. By default an exception is thrown.
///
public CyclicInvokeMode CylicInvokeResponse = CyclicInvokeMode.ThrowException;
public Nullable MaxNumberOfElements { get; set; }
public Memorizer(Func getKey = null,
Func extractData = null)
{
if (extractData != null)
getData = extractData;
this.getKey = getKey;
}
///
/// Forces manual update of constraints.
///
public void CheckConstraints()
{
while (checkSizeConstraints() == Status.Changed) { }
}
enum Status
{
Changed,
Unchanged
}
Status checkSizeConstraints()
{
if (SoftSizeDecayTime < TimeSpan.MaxValue || MaxNumberOfElements.HasValue
&& (ulong) memorizerTable.Count > MaxNumberOfElements.Value)
{
lock (memorizerTable)
{
var removeKey = lastUse.Keys.FindMin(key2 => lastUse[key2]);
if (removeKey != null)
{
if (SoftSizeDecayTime < DateTime.UtcNow - lastUse[removeKey])
{
lastUse.Remove(removeKey);
memorizerTable.Remove(removeKey);
return Status.Changed;
}
else if (MaxNumberOfElements.HasValue
&& (ulong) memorizerTable.Count > MaxNumberOfElements.Value)
{
lastUse.Remove(removeKey);
memorizerTable.Remove(removeKey);
return Status.Changed;
}
}
}
}
return Status.Unchanged;
}
MemorizerKey invokeGetKey(ArgT arg)
{
return getKey == null ? (MemorizerKey)(object)arg : getKey(arg);
}
public virtual ResultT OnCyclicCallDetected(ArgT key)
{
throw new Exception("Cyclic memorizer invoke detected.");
}
public ResultT this[ArgT arg] => Invoke(arg);
public ResultT Invoke(ArgT arg)
{
var key = invokeGetKey(arg);
if(Validator != null){
var obj = Validator(key);
lock (memorizerTable)
{
if (validatorData.TryGetValue(key, out object value))
{
if (false == Equals(value, obj))
{
Invalidate(arg);
validatorData[key] = obj;
}
}else
{
validatorData[key] = obj;
}
}
}
LockObject lockObj;
lock (memorizerTable)
{
lastUse[key] = DateTime.UtcNow;
if (!locks.TryGetValue(key, out lockObj))
{
lockObj = new LockObject();
locks[key] = lockObj;
}
}
lock (lockObj)
{
if (lockObj.IsLocked)
{ // Avoid running into a StackOverflowException.
if (CylicInvokeResponse == CyclicInvokeMode.ThrowException)
return OnCyclicCallDetected(arg);
return default(ResultT);
}
try
{
lockObj.IsLocked = true;
lock (memorizerTable)
{
if (memorizerTable.TryGetValue(key, out ResultT value))
return value;
}
ResultT o = getData(arg);
lock (memorizerTable)
{
memorizerTable[key] = o;
checkSizeConstraints();
}
return o;
}
finally
{
lockObj.IsLocked = false;
}
}
}
public ResultT GetCached(ArgT arg)
{
ResultT o = default(ResultT);
var key = invokeGetKey(arg);
lock (memorizerTable)
{
if (!memorizerTable.TryGetValue(key, out o))
return default(ResultT);
lastUse[key] = DateTime.UtcNow;;
}
return o;
}
public void Add(ArgT arg, ResultT value)
{
var key = invokeGetKey(arg);
lock (memorizerTable)
{
lastUse[key] = DateTime.UtcNow;
memorizerTable[key] = value;
checkSizeConstraints();
}
}
public void Invalidate(ArgT value)
{
var key = invokeGetKey(value);
lock (memorizerTable)
{
memorizerTable.Remove(key);
lastUse.Remove(key);
validatorData.Remove(key);
}
}
///
/// Invalidate the keys where f returns true. This is being done while
/// the memorizer is locked, so race conditions are avoided.
///
///
public void InvalidateWhere(Func predicate)
{
lock (memorizerTable)
{
List keys = null;
foreach(var item in memorizerTable)
{
if(predicate(item.Key, item.Value))
{
if(keys == null)
{
keys = new List();
}
keys.Add(item.Key);
}
}
if(keys != null)
{
foreach(var k in keys)
{
memorizerTable.Remove(k);
lastUse.Remove(k);
}
}
}
}
public List GetResults()
{
lock (memorizerTable)
{
return memorizerTable.Values.ToList();
}
}
public void InvalidateAll()
{
lock (memorizerTable)
{
memorizerTable.Clear();
lastUse.Clear();
validatorData.Clear();
}
}
}
internal class Memorizer : Memorizer
{
public Memorizer(Func func) : base(extractData: func)
{
}
}
static class Utils
{
static readonly char[] padding = { '=' };
public static string Base64UrlEncode(byte[] bytes)
{
return Convert.ToBase64String(bytes)
.TrimEnd(padding).Replace('+', '-')
.Replace('/', '_');
}
public static IEnumerable<(int, T)> WithIndex(this IEnumerable collection)
{
return collection.Select((ele, index) => (index, ele));
}
///
/// Thread-safe and lock free value exchange. valueGen depends on the current value.
///
/// Where to place the value
/// Function generating a value based on the current value.
/// The type of object.
public static void InterlockedSwap(ref T outPlace, Func valueGen) where T : class
{
while (true)
{
// safely add a new item to the list using the compare-and-swap atomic operation.
var currentValue = outPlace;
var nextValue = valueGen();
if (Interlocked.CompareExchange(ref outPlace, nextValue, currentValue) == currentValue)
{
break;
}
}
}
#if DEBUG
public static readonly bool IsDebugBuild = true;
#else
public static readonly bool IsDebugBuild = false;
#endif
/// Swaps two variables
public static void Swap(ref T a, ref T b) => (a, b) = (b, a);
/// Do nothing.
public static void Noop()
{
}
///
/// Returns the element for which selector returns the max value.
/// if IEnumerable is empty, it returns default(T) multiplier gives the direction to search.
///
static T FindExtreme(this IEnumerable sequence, Func selector, int multiplier)
where C : IComparable
{
var e = sequence.GetEnumerator();
if (!e.MoveNext())
return default(T);
T selected = e.Current;
C max = selector(selected);
while (e.MoveNext())
{
var obj = e.Current;
C comparable = selector(obj);
if (comparable.CompareTo(max) * multiplier > 0)
{
selected = obj;
max = comparable;
}
}
return selected;
}
/// Returns the element for which selector returns the max value. if IEnumerable is empty, it returns default(T).
public static T FindMax(this IEnumerable ienumerable, Func selector) where C : IComparable
{
return FindExtreme(ienumerable, selector, 1);
}
///
/// Returns the element for which selector returns the minimum value.
/// if IEnumerable is empty, it returns default(T).
///
public static T FindMin(this IEnumerable ienumerable, Func selector) where C : IComparable
{
return FindExtreme(ienumerable, selector, -1);
}
///
/// Skips last N items.
///
///
///
/// n last items to skip.
///
public static IEnumerable SkipLastN(this IEnumerable source, int n)
{
var list = source.ToList();
if ((list.Count - n) > 0)
return list.Take(list.Count - n);
else
return Enumerable.Empty();
}
///
/// Removes items of source matching a given predicate.
///
///
///
public static void RemoveIf(this IList source, Predicate pred)
{
if (source is List lst)
{
lst.RemoveAll(pred);
return;
}
for (int i = source.Count - 1; i >= 0; i--)
{
if (pred(source[i]))
{
source.RemoveAt(i);
}
}
}
///
/// Removes items of source matching a given predicate.
///
///
///
public static void RemoveIf(this IList source, Predicate pred)
{
for (int i = source.Count - 1; i >= 0; i--)
{
if (pred(source[i]))
{
source.RemoveAt(i);
}
}
}
private static void flattenHeirarchy(IEnumerable lst, Func> lookup, IList result,
HashSet found)
{
foreach (var item in lst)
{
if (found != null)
{
if (found.Contains(item))
continue;
found.Add(item);
}
result.Add(item);
var sublist = lookup(item);
if (sublist != null)
flattenHeirarchy(sublist, lookup, result, found);
}
}
///
/// Flattens a recursive IEnumerable.
///
///
///
/// Returns a list of the next level of elements. The returned value is allowed to be null and will in this case be treated like an empty list.
/// True if only one of each element should be inserted in the list.
/// Buffer to use instead of creating a new list to store the values. This can be used to avoid allocation.
///
public static List FlattenHeirarchy(IEnumerable lst, Func> lookup,
bool distinct = false, List buffer = null)
{
if (buffer != null)
buffer.Clear();
else
buffer = new List();
flattenHeirarchy(lst, lookup, buffer, distinct ? new HashSet() : null);
return buffer;
}
public static void FlattenHeirarchyInto(IEnumerable lst, Func> lookup, ISet set)
{
foreach (var item in lst)
{
if (set.Add(item))
{
var sublist = lookup(item);
if (sublist != null)
FlattenHeirarchyInto(sublist, lookup, set);
}
}
}
public static void ForEach(this IEnumerable source, Action func)
{
foreach (var item in source)
{
func(item);
}
}
///
/// Appends a range of elements to an IEnumerable.
///
///
///
///
///
public static IEnumerable Append(this IEnumerable source, params T[] newObjects)
{
return source.Concat(newObjects);
}
/// First index where the result of predicate function is true.
public static int IndexWhen(this IEnumerable source, Func pred)
{
int idx = 0;
foreach (var item in source)
{
if (pred(item))
{
return idx;
}
idx++;
}
return -1;
}
///
/// Returns true if the generic IEnumerable is empty
///
///
///
public static bool IsEnumerableEmpty(this IEnumerable enu)
{
var e = enu.GetEnumerator();
bool empty = e.MoveNext() == false;
(e as IDisposable)?.Dispose();
return empty;
}
///
/// Returns true if the source is longer than count elements.
///
///
///
///
///
public static bool IsLongerThan(this IEnumerable source, long count)
{
foreach (var _ in source)
if (--count < 0)
return true;
return false;
}
///
/// Creates a HashSet from an IEnumerable.
///
public static HashSet ToHashSet(this IEnumerable source)
{
return new HashSet(source);
}
///
/// Creates a HashSet from an IEnumerable, with a specialized comparer.
///
public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer comparer)
{
return new HashSet(source, comparer);
}
///
/// The opposite of Where.
///
///
///
///
///
public static IEnumerable Except(this IEnumerable source, Func selector)
{
foreach (var x in source)
if (selector(x) == false)
yield return x;
}
/// As 'Select' but skipping null values.
/// Short hand for/more efficient version of 'Select(f).Where(x => x != null)'
///
///
///
///
///
public static IEnumerable SelectValues(this IEnumerable source, Func f)
{
foreach (var x in source)
{
var value = f(x);
if (value != null)
yield return value;
}
}
/// As 'Select and FirstOrDefault' but skipping null values.
/// Short hand for/more efficient version of 'Select(f).Where(x => x != null).FirstOrDefault()'
///
public static T2 FirstNonDefault(this IEnumerable source, Func selector)
{
foreach (var x in source)
{
var value = selector(x);
if (Equals(value, default(T2)) == false)
return value;
}
return default(T2);
}
///
/// Merged a dictionary into another, overwriting colliding keys.
///
///
///
///
///
public static void MergeInto(this Dictionary srcDict, Dictionary dstDict)
{
foreach (var kv in srcDict)
{
dstDict[kv.Key] = kv.Value;
}
}
struct OnceLogToken
{
public object Token;
public TraceSource Log;
}
static HashSet logOnceTokens = new HashSet();
///
/// Avoids spamming the log with errors that
/// should only be shown once by memorizing token and TraceSource.
///
/// True if an error was logged.
public static bool ErrorOnce(this TraceSource log, object token, string message, params object[] args)
{
lock (logOnceTokens)
{
var logtoken = new OnceLogToken { Token = token, Log = log };
if (!logOnceTokens.Contains(logtoken))
{
log.Error(message, args);
logOnceTokens.Add(logtoken);
return true;
}
return false;
}
}
public static string ConvertToUnsecureString(this System.Security.SecureString securePassword)
{
if (securePassword == null)
throw new ArgumentNullException("securePassword");
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
return Marshal.PtrToStringUni(unmanagedString);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
}
}
public static System.Security.SecureString ToSecureString(this string str)
{
System.Security.SecureString result = new System.Security.SecureString();
foreach (var c in str)
result.AppendChar(c);
return result;
}
public static bool IsNumeric(object obj)
{
switch (obj)
{
case float _: return true;
case double _: return true;
case decimal _: return true;
case byte _: return true;
case char _: return true;
case sbyte _: return true;
case short _: return true;
case ushort _: return true;
case int _: return true;
case uint _: return true;
case long _: return true;
case ulong _: return true;
default: return false;
}
}
public static bool Compatible(Version searched, Version referenced)
{
if (searched == null) return true;
if (searched.Major != referenced.Major) return false;
if (searched.Minor >= referenced.Minor) return true;
return false;
}
///
///
///
///
///
///
///
///
public static T SetFlag(this T e, T flag, bool enabled) where T : struct
{
if (e is Enum == false)
throw new InvalidOperationException("T must be an enum");
int _e = (int)Convert.ChangeType(e, typeof(int));
int _flag = (int)Convert.ChangeType(flag, typeof(int));
int r;
if (enabled)
r = (_e | _flag);
else
r = (_e & ~_flag);
return (T)Enum.ToObject(typeof(T), r);
}
public static string EnumToReadableString(Enum value)
{
if (value == null) return null;
var enumType = value.GetType();
var mem = enumType.GetMember(value.ToString()).FirstOrDefault();
if (mem != null)
{
return TranslationManager.TranslateEnum(value).Name;
}
if (false == enumType.HasAttribute())
return value.ToString();
var zeroValue = Enum.ToObject(enumType, 0);
if (value.Equals(zeroValue))
return ""; // this does not happen if zeroValue is declared.
var flags = Enum.GetValues(enumType).OfType();
var activeFlags = flags.Where(value.HasFlag).Except(f => f.Equals(zeroValue));
var result = string.Join(" | ", activeFlags.Select(EnumToReadableString));
if (string.IsNullOrEmpty(result) == false) return result;
// last resort.
var val = (long)Convert.ChangeType(value, TypeCode.Int64);
return val.ToString();
}
public static string EnumToDescription(Enum value)
{
if (value == null) return null;
var enumType = value.GetType();
var mem = enumType.GetMember(value.ToString()).FirstOrDefault();
// if member is null, fall back to the readable enum string (or description is null)
return mem?.GetDisplayAttribute().Description ?? EnumToReadableString(value);
}
public static string SerializeToString(this TestPlan plan, bool throwOnErrors = false)
{
using (var mem = new MemoryStream())
{
var serializer = new TapSerializer();
plan.Save(mem, serializer);
if (throwOnErrors && serializer.Errors.Any())
throw new Exception(string.Join("\n", serializer.Errors));
return Encoding.UTF8.GetString(mem.ToArray());
}
}
public static object DeserializeFromString(string str)
{
return new TapSerializer().DeserializeFromString(str);
}
public static T DeserializeFromString(string str) => (T)DeserializeFromString(str);
class ActionDisposable : IDisposable
{
Action action;
public ActionDisposable(Action action) => this.action = action;
public void Dispose()
{
action();
action = null;
}
}
public static IDisposable WithDisposable(Action action)
{
return new ActionDisposable(action);
}
/// Gets or creates a value based on the key. This is useful for caches.
public static V GetOrCreateValue(this Dictionary dictionary, K key, Func createValue)
{
if (dictionary.TryGetValue(key, out V value))
return value;
return dictionary[key] = createValue(key);
}
public static string BytesToReadable(long bytes)
{
if (bytes < 1000) return $"{bytes} B";
if (bytes < 1000000) return $"{bytes / 1000.0:0.00} kB";
if (bytes < 1000000000) return $"{bytes / 1000000.0:0.00} MB";
return $"{bytes / 1000000000.0:0.00} GB";
}
/// Backoff strategy for Retry.
static void Backoff(int retry, int sleepBaseMs = 100)
{
const double backoffFactor = 2.0;
const double jitterFactor = 0.2;
double baseSleep = Math.Pow(backoffFactor, retry) * sleepBaseMs;
// add a bit of jitter to avoid "thundering herd" problems.
Random random = new Random();
double jitterAmount = 1.0 + jitterFactor * (random.NextDouble() - 0.5);
int totalSleep = (int)(baseSleep * jitterAmount);
if (totalSleep > 0)
TapThread.Sleep(totalSleep);
}
/// Retries calling a function a number of times if calling it fails.
/// The function to call.
/// an exception type to retry on. By default this is typeof(Exception)
/// default 5 times
/// more exceptions to retry on
/// The time to sleep at first iteration. Subsequent sleeps with be longer
public static void Retry(Action function, Type retryOn = null, int maxRetries = 5, Type[] moreExceptions = null,
int sleepBaseMs = 100)
{
Retry(() =>
{
function();
return 0;
}, retryOn, maxRetries, moreExceptions, sleepBaseMs);
}
/// Retries calling a function a number of times if calling it fails.
/// The function to call.
/// an exception type to retry on. By default this is typeof(Exception)
/// default 5 times
/// more exceptions to retry on
/// The time to sleep at first iteration. Subsequent sleeps with be longer
/// the return type.
/// The return value
public static T Retry(Func function, Type retryOn = null, int maxRetries = 5, Type[] moreExceptions = null,
int sleepBaseMs = 100)
{
if (retryOn == null)
retryOn = typeof(Exception);
var allExceptions = new[] { retryOn }.Concat(moreExceptions ?? Array.Empty()).ToArray();
for (int i = 0; i < maxRetries - 1; i++)
{
try
{
return function();
}
catch (Exception ex)
{
while (ex is AggregateException a && a.InnerExceptions.Count == 1)
{
ex = a.InnerExceptions[0];
}
var exType = ex.GetType();
if (allExceptions.Any(x => x.IsAssignableFrom(exType)))
{
// if one of the expected exceptions.
Backoff(i, sleepBaseMs);
continue;
}
ExceptionDispatchInfo.Capture(ex).Throw();
}
}
// on the last attempt just call the function directly
return function();
}
}
static internal class Sequence
{
/// Turns item into a one element array, unless it is null.
public static T[] AsSingle(this T item) => item == null ? Array.Empty() : new[] {item};
///
/// Like distinct but keeps the last item. Returns List because we need to iterate until last element anyway.
///
///
///
///
public static List DistinctLast(this IEnumerable items)
{
Dictionary d = new Dictionary();
int i = 0;
foreach (var item in items)
{
d[item] = i;
i++;
}
return d.OrderBy(kv => kv.Value).Select(kv => kv.Key).ToList();
}
internal static int ProcessPattern(IEnumerator objs, Action f1)
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1;
}
}
return 0;
}
internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2 )
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1 + ProcessPattern(objs, f2);
case T2 t:
f2(t);
return 1 + ProcessPattern(objs, f1);
}
}
return 0;
}
internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3 )
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1 + ProcessPattern(objs, f2, f3);
case T2 t:
f2(t);
return 1 + ProcessPattern(objs, f1, f3);
case T3 t:
f3(t);
return 1 + ProcessPattern(objs, f1, f2);
}
}
return 0;
}
/// Adds elements that arent null to the list.
internal static void AddExceptNull(this ICollection list, T x)
{
if (x != null)
list.Add(x);
}
internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 )
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1 + ProcessPattern(objs, f2, f3, f4);
case T2 t:
f2(t);
return 1 + ProcessPattern(objs, f1, f3, f4);
case T3 t:
f3(t);
return 1 + ProcessPattern(objs, f1, f2, f4);
case T4 t:
f4(t);
return 1 + ProcessPattern(objs, f1, f2, f3);
}
}
return 0;
}
public static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 , Action f5 )
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1 + ProcessPattern(objs, f2, f3, f4, f5);
case T2 t:
f2(t);
return 1 + ProcessPattern(objs, f1, f3, f4, f5);
case T3 t:
f3(t);
return 1 + ProcessPattern(objs, f1, f2, f4, f5);
case T4 t:
f4(t);
return 1 + ProcessPattern(objs, f1, f2, f3, f5);
case T5 t:
f5(t);
return 1 + ProcessPattern(objs, f1, f2, f3, f4);
}
}
return 0;
}
public static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 , Action f5, Action f6 )
{
while (objs.MoveNext())
{
switch (objs.Current)
{
case T1 t:
f1(t);
return 1 + ProcessPattern(objs, f2, f3, f4, f5, f6);
case T2 t:
f2(t);
return 1 + ProcessPattern(objs, f1, f3, f4, f5, f6);
case T3 t:
f3(t);
return 1 + ProcessPattern(objs, f1, f2, f4, f5,f6);
case T4 t:
f4(t);
return 1 + ProcessPattern(objs, f1, f2, f3, f5,f6);
case T5 t:
f5(t);
return 1 + ProcessPattern(objs, f1, f2, f3, f4,f6);
case T6 t:
f6(t);
return 1 + ProcessPattern(objs, f1, f2, f3, f4, f5);
}
}
return 0;
}
public static int ProcessPattern(IEnumerable objs, Action f1, Action f2)
{
using (var e = objs.GetEnumerator())
return ProcessPattern(e, f1, f2);
}
public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3)
{
using (var e = objs.GetEnumerator())
return ProcessPattern(e, f1, f2, f3);
}
public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4)
{
using (var e = objs.GetEnumerator())
return ProcessPattern(e, f1, f2, f3, f4);
}
public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4, Action f5)
{
using (var e = objs.GetEnumerator())
return ProcessPattern(e, f1, f2, f3, f4, f5);
}
public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4, Action f5, Action f6)
{
using (var e = objs.GetEnumerator())
return ProcessPattern(e, f1, f2, f3, f4, f5, f6);
}
///
/// Count the number of elements in an enumerable.
///
public static int Count(this IEnumerable enumerable)
{
if (enumerable is ICollection col)
return col.Count;
int c = 0;
foreach (var _ in enumerable)
c++;
return c;
}
///
/// iterates lists and generates pairs of each list. Once the end is reached for one of the lists, execution stops.
///
public static IEnumerable<(T1, T2)> Pairwise(this IEnumerable a, IEnumerable b)
{
using(var ia = a.GetEnumerator())
using (var ib = b.GetEnumerator())
{
while (ia.MoveNext() && ib.MoveNext())
{
yield return (ia.Current, ib.Current);
}
}
}
///
/// process the enumerable source N objects at a time.
///
public static IEnumerable Batch(this IEnumerable src, int N)
{
var buffer = new T[N];
int cnt = 0;
foreach (var elem in src)
{
buffer[cnt] = elem;
cnt += 1;
if (cnt == N)
{
foreach (var elem2 in buffer)
yield return elem2;
cnt = 0;
}
}
foreach (var elem2 in buffer.Take(cnt))
yield return elem2;
}
public static void Append(ref T[] array, params T[] appendage)
{
int preLen = array.Length;
Array.Resize(ref array, array.Length + appendage.Length);
Array.Copy(appendage, 0, array, preLen, appendage.Length);
}
public static IEnumerable TrySelect(this IEnumerable src, Func f,
Action handler) => src.TrySelect(f, handler);
public static IEnumerable TrySelect(this IEnumerable src, Func f,
Action handler) => src.TrySelect(f, (e,v) => handler(e));
public static IEnumerable TrySelect(this IEnumerable src, Func f, Action handler) where T3: Exception
{
foreach (var x in src)
{
T2 y;
try
{
y = f(x);
}
catch(T3 e)
{
handler(e, x);
continue;
}
yield return y;
}
}
}
internal static class Time
{
///
/// A TimeSpan from seconds that does not round to nearest millisecond. (TimeSpan.FromSeconds does that).
///
///
///
public static TimeSpan FromSeconds(double seconds)
{
try
{
checked
{
// if the multiplication here creates a number greater than
// long.Max (or overflows negatively), we just return
// TimeSpan.MaxValue/MinValue.
long ticks = (long)(seconds * 1e7);
return TimeSpan.FromTicks(ticks);
}
}
catch (OverflowException)
{
if (double.IsNaN(seconds))
throw new ArithmeticException("Nan is not a supported time value.");
if (seconds < 0)
return TimeSpan.MinValue;
return TimeSpan.MaxValue;
}
}
}
/// Invoke an action after a timeout, unless canceled.
class TimeoutOperation : IDisposable
{
/// Estimate of how long it takes for the user to loose patience.
static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2);
TimeoutOperation(TimeSpan timeout, Action action)
{
this.timeout = timeout;
this.action = action;
tokenSource = new CancellationTokenSource(timeout);
}
readonly Action action;
readonly TimeSpan timeout;
readonly CancellationTokenSource tokenSource;
bool isCompleted;
void wait()
{
try
{
var token = tokenSource.Token;
if (!token.IsCancellationRequested && WaitHandle.WaitTimeout == WaitHandle.WaitAny(new [] { token.WaitHandle, TapThread.Current.AbortToken.WaitHandle }, timeout))
action();
}
finally
{
lock (tokenSource)
{
tokenSource.Dispose();
isCompleted = true;
}
}
}
/// Creates a new TimeoutOperation with a specific timeout.
///
///
///
public static TimeoutOperation Create(TimeSpan timeout, Action actionOnTimeout)
{
TimeoutOperation operation = new TimeoutOperation(timeout, actionOnTimeout);
TapThread.Start(operation.wait, "Timeout");
return operation;
}
/// Creates a timeout operation with the default timeout.
///
///
public static TimeoutOperation Create(Action actionOnTimeout)
{
return Create(DefaultTimeout, actionOnTimeout);
}
///
/// Cancel invoking the action after the timeout.
///
public void Cancel()
{
try
{
lock (tokenSource)
{
if (!isCompleted)
tokenSource.Cancel();
}
}
catch (ObjectDisposedException)
{
}
}
public void Dispose()
{
Cancel();
}
}
}