diff --git a/API.Tests/Helpers/PrivateObjectPrivateType.cs b/API.Tests/Helpers/PrivateObjectPrivateType.cs
new file mode 100644
index 000000000..e99016828
--- /dev/null
+++ b/API.Tests/Helpers/PrivateObjectPrivateType.cs
@@ -0,0 +1,1864 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
+
+namespace Microsoft.VisualStudio.TestTools.UnitTesting
+{
+ ///
+ /// This class represents the live NON public INTERNAL object in the system
+ ///
+ public class PrivateObject
+ {
+ // bind everything
+ private const BindingFlags BindToEveryThing = BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;
+
+ private static BindingFlags constructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.NonPublic;
+
+ private object target; // automatically initialized to null
+ private Type originalType; // automatically initialized to null
+
+ private Dictionary> methodCache; // automatically initialized to null
+
+ ///
+ /// Initializes a new instance of the class that contains
+ /// the already existing object of the private class
+ ///
+ /// object that serves as starting point to reach the private members
+ /// the derefrencing string using . that points to the object to be retrived as in m_X.m_Y.m_Z
+ public PrivateObject(object obj, string memberToAccess)
+ {
+ ValidateAccessString(memberToAccess);
+
+ PrivateObject temp = obj as PrivateObject;
+ if (temp == null)
+ {
+ temp = new PrivateObject(obj);
+ }
+
+ // Split The access string
+ string[] arr = memberToAccess.Split(new char[] { '.' });
+
+ for (int i = 0; i < arr.Length; i++)
+ {
+ object next = temp.InvokeHelper(arr[i], BindToEveryThing | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty, null, CultureInfo.InvariantCulture);
+ temp = new PrivateObject(next);
+ }
+
+ this.target = temp.target;
+ this.originalType = temp.originalType;
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps the
+ /// specified type.
+ ///
+ /// Name of the assembly
+ /// fully qualified name
+ /// Argmenets to pass to the constructor
+ public PrivateObject(string assemblyName, string typeName, params object[] args)
+ : this(assemblyName, typeName, null, args)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps the
+ /// specified type.
+ ///
+ /// Name of the assembly
+ /// fully qualified name
+ /// An array of objects representing the number, order, and type of the parameters for the constructor to get
+ /// Argmenets to pass to the constructor
+ public PrivateObject(string assemblyName, string typeName, Type[] parameterTypes, object[] args)
+ : this(Type.GetType(string.Format(CultureInfo.InvariantCulture, "{0}, {1}", typeName, assemblyName), false), parameterTypes, args)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps the
+ /// specified type.
+ ///
+ /// type of the object to create
+ /// Argmenets to pass to the constructor
+ public PrivateObject(Type type, params object[] args)
+ : this(type, null, args)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps the
+ /// specified type.
+ ///
+ /// type of the object to create
+ /// An array of objects representing the number, order, and type of the parameters for the constructor to get
+ /// Argmenets to pass to the constructor
+ public PrivateObject(Type type, Type[] parameterTypes, object[] args)
+ {
+ object o;
+ if (parameterTypes != null)
+ {
+ ConstructorInfo ci = type.GetConstructor(BindToEveryThing, null, parameterTypes, null);
+ if (ci == null)
+ {
+ throw new ArgumentException("The constructor with the specified signature could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.");
+ }
+
+ try
+ {
+ o = ci.Invoke(args);
+ }
+ catch (TargetInvocationException e)
+ {
+ Debug.Assert(e.InnerException != null, "Inner exception should not be null.");
+ if (e.InnerException != null)
+ {
+ throw e.InnerException;
+ }
+
+ throw;
+ }
+ }
+ else
+ {
+ o = Activator.CreateInstance(type, constructorFlags, null, args, null);
+ }
+
+ this.ConstructFrom(o);
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps
+ /// the given object.
+ ///
+ /// object to wrap
+ public PrivateObject(object obj)
+ {
+ this.ConstructFrom(obj);
+ }
+
+ ///
+ /// Initializes a new instance of the class that wraps
+ /// the given object.
+ ///
+ /// object to wrap
+ /// PrivateType object
+ public PrivateObject(object obj, PrivateType type)
+ {
+ this.target = obj;
+ this.originalType = type.ReferencedType;
+ }
+
+ ///
+ /// Gets or sets the target
+ ///
+ public object Target
+ {
+ get
+ {
+ return this.target;
+ }
+
+ set
+ {
+ this.target = value;
+ this.originalType = value.GetType();
+ }
+ }
+
+ ///
+ /// Gets the type of underlying object
+ ///
+ public Type RealType
+ {
+ get
+ {
+ return this.originalType;
+ }
+ }
+
+ private Dictionary> GenericMethodCache
+ {
+ get
+ {
+ if (this.methodCache == null)
+ {
+ this.BuildGenericMethodCacheForType(this.originalType);
+ }
+
+ Debug.Assert(this.methodCache != null, "Invalid method cache for type.");
+
+ return this.methodCache;
+ }
+ }
+
+ ///
+ /// returns the hash code of the target object
+ ///
+ /// int representing hashcode of the target object
+ public override int GetHashCode()
+ {
+ Debug.Assert(this.target != null, "target should not be null.");
+ return this.target.GetHashCode();
+ }
+
+ ///
+ /// Equals
+ ///
+ /// Object with whom to compare
+ /// returns true if the objects are equal.
+ public override bool Equals(object obj)
+ {
+ if (this != obj)
+ {
+ Debug.Assert(this.target != null, "target should not be null.");
+ if (typeof(PrivateObject) == obj?.GetType())
+ {
+ return this.target.Equals(((PrivateObject)obj).target);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// Arguments to pass to the member to invoke.
+ /// Result of method call
+ public object Invoke(string name, params object[] args)
+ {
+ return this.Invoke(name, null, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// Result of method call
+ public object Invoke(string name, Type[] parameterTypes, object[] args)
+ {
+ return this.Invoke(name, parameterTypes, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// An array of types corresponding to the types of the generic arguments.
+ /// Result of method call
+ public object Invoke(string name, Type[] parameterTypes, object[] args, Type[] typeArguments)
+ {
+ return this.Invoke(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// Arguments to pass to the member to invoke.
+ /// Culture info
+ /// Result of method call
+ public object Invoke(string name, object[] args, CultureInfo culture)
+ {
+ return this.Invoke(name, null, args, culture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// Culture info
+ /// Result of method call
+ public object Invoke(string name, Type[] parameterTypes, object[] args, CultureInfo culture)
+ {
+ return this.Invoke(name, BindToEveryThing, parameterTypes, args, culture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// Arguments to pass to the member to invoke.
+ /// Result of method call
+ public object Invoke(string name, BindingFlags bindingFlags, params object[] args)
+ {
+ return this.Invoke(name, bindingFlags, null, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// Result of method call
+ public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args)
+ {
+ return this.Invoke(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// Arguments to pass to the member to invoke.
+ /// Culture info
+ /// Result of method call
+ public object Invoke(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture)
+ {
+ return this.Invoke(name, bindingFlags, null, args, culture);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// Culture info
+ /// Result of method call
+ public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture)
+ {
+ return this.Invoke(name, bindingFlags, parameterTypes, args, culture, null);
+ }
+
+ ///
+ /// Invokes the specified method
+ ///
+ /// Name of the method
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// An array of objects representing the number, order, and type of the parameters for the method to get.
+ /// Arguments to pass to the member to invoke.
+ /// Culture info
+ /// An array of types corresponding to the types of the generic arguments.
+ /// Result of method call
+ public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments)
+ {
+ if (parameterTypes != null)
+ {
+ bindingFlags |= BindToEveryThing | BindingFlags.Instance;
+
+ // Fix up the parameter types
+ MethodInfo member = this.originalType.GetMethod(name, bindingFlags, null, parameterTypes, null);
+
+ // If the method was not found and type arguments were provided for generic paramaters,
+ // attempt to look up a generic method.
+ if ((member == null) && (typeArguments != null))
+ {
+ // This method may contain generic parameters...if so, the previous call to
+ // GetMethod() will fail because it doesn't fully support generic parameters.
+
+ // Look in the method cache to see if there is a generic method
+ // on the incoming type that contains the correct signature.
+ member = this.GetGenericMethodFromCache(name, parameterTypes, typeArguments, bindingFlags, null);
+ }
+
+ if (member == null)
+ {
+ throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ try
+ {
+ if (member.IsGenericMethodDefinition)
+ {
+ MethodInfo constructed = member.MakeGenericMethod(typeArguments);
+ return constructed.Invoke(this.target, bindingFlags, null, args, culture);
+ }
+ else
+ {
+ return member.Invoke(this.target, bindingFlags, null, args, culture);
+ }
+ }
+ catch (TargetInvocationException e)
+ {
+ Debug.Assert(e.InnerException != null, "Inner exception should not be null.");
+ if (e.InnerException != null)
+ {
+ throw e.InnerException;
+ }
+
+ throw;
+ }
+ }
+ else
+ {
+ return this.InvokeHelper(name, bindingFlags | BindingFlags.InvokeMethod, args, culture);
+ }
+ }
+
+ ///
+ /// Gets the array element using array of subsrcipts for each dimension
+ ///
+ /// Name of the member
+ /// the indices of array
+ /// An arrya of elements.
+ public object GetArrayElement(string name, params int[] indices)
+ {
+ return this.GetArrayElement(name, BindToEveryThing, indices);
+ }
+
+ ///
+ /// Sets the array element using array of subsrcipts for each dimension
+ ///
+ /// Name of the member
+ /// Value to set
+ /// the indices of array
+ public void SetArrayElement(string name, object value, params int[] indices)
+ {
+ this.SetArrayElement(name, BindToEveryThing, value, indices);
+ }
+
+ ///
+ /// Gets the array element using array of subsrcipts for each dimension
+ ///
+ /// Name of the member
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// the indices of array
+ /// An arrya of elements.
+ public object GetArrayElement(string name, BindingFlags bindingFlags, params int[] indices)
+ {
+ Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture);
+ return arr.GetValue(indices);
+ }
+
+ ///
+ /// Sets the array element using array of subsrcipts for each dimension
+ ///
+ /// Name of the member
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// Value to set
+ /// the indices of array
+ public void SetArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices)
+ {
+ Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture);
+ arr.SetValue(value, indices);
+ }
+
+ ///
+ /// Get the field
+ ///
+ /// Name of the field
+ /// The field.
+ public object GetField(string name)
+ {
+ return this.GetField(name, BindToEveryThing);
+ }
+
+ ///
+ /// Sets the field
+ ///
+ /// Name of the field
+ /// value to set
+ public void SetField(string name, object value)
+ {
+ this.SetField(name, BindToEveryThing, value);
+ }
+
+ ///
+ /// Gets the field
+ ///
+ /// Name of the field
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// The field.
+ public object GetField(string name, BindingFlags bindingFlags)
+ {
+ return this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Sets the field
+ ///
+ /// Name of the field
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// value to set
+ public void SetField(string name, BindingFlags bindingFlags, object value)
+ {
+ this.InvokeHelper(name, BindingFlags.SetField | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Get the field or property
+ ///
+ /// Name of the field or property
+ /// The field or property.
+ public object GetFieldOrProperty(string name)
+ {
+ return this.GetFieldOrProperty(name, BindToEveryThing);
+ }
+
+ ///
+ /// Sets the field or property
+ ///
+ /// Name of the field or property
+ /// value to set
+ public void SetFieldOrProperty(string name, object value)
+ {
+ this.SetFieldOrProperty(name, BindToEveryThing, value);
+ }
+
+ ///
+ /// Gets the field or property
+ ///
+ /// Name of the field or property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// The field or property.
+ public object GetFieldOrProperty(string name, BindingFlags bindingFlags)
+ {
+ return this.InvokeHelper(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Sets the field or property
+ ///
+ /// Name of the field or property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// value to set
+ public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object value)
+ {
+ this.InvokeHelper(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Gets the property
+ ///
+ /// Name of the property
+ /// Arguments to pass to the member to invoke.
+ /// The property.
+ public object GetProperty(string name, params object[] args)
+ {
+ return this.GetProperty(name, null, args);
+ }
+
+ ///
+ /// Gets the property
+ ///
+ /// Name of the property
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ /// The property.
+ public object GetProperty(string name, Type[] parameterTypes, object[] args)
+ {
+ return this.GetProperty(name, BindToEveryThing, parameterTypes, args);
+ }
+
+ ///
+ /// Set the property
+ ///
+ /// Name of the property
+ /// value to set
+ /// Arguments to pass to the member to invoke.
+ public void SetProperty(string name, object value, params object[] args)
+ {
+ this.SetProperty(name, null, value, args);
+ }
+
+ ///
+ /// Set the property
+ ///
+ /// Name of the property
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// value to set
+ /// Arguments to pass to the member to invoke.
+ public void SetProperty(string name, Type[] parameterTypes, object value, object[] args)
+ {
+ this.SetProperty(name, BindToEveryThing, value, parameterTypes, args);
+ }
+
+ ///
+ /// Gets the property
+ ///
+ /// Name of the property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// Arguments to pass to the member to invoke.
+ /// The property.
+ public object GetProperty(string name, BindingFlags bindingFlags, params object[] args)
+ {
+ return this.GetProperty(name, bindingFlags, null, args);
+ }
+
+ ///
+ /// Gets the property
+ ///
+ /// Name of the property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ /// The property.
+ public object GetProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args)
+ {
+ if (parameterTypes != null)
+ {
+ PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null);
+ if (pi == null)
+ {
+ throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ return pi.GetValue(this.target, args);
+ }
+ else
+ {
+ return this.InvokeHelper(name, bindingFlags | BindingFlags.GetProperty, args, null);
+ }
+ }
+
+ ///
+ /// Sets the property
+ ///
+ /// Name of the property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// value to set
+ /// Arguments to pass to the member to invoke.
+ public void SetProperty(string name, BindingFlags bindingFlags, object value, params object[] args)
+ {
+ this.SetProperty(name, bindingFlags, value, null, args);
+ }
+
+ ///
+ /// Sets the property
+ ///
+ /// Name of the property
+ /// A bitmask comprised of one or more that specify how the search is conducted.
+ /// value to set
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ public void SetProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args)
+ {
+ if (parameterTypes != null)
+ {
+ PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null);
+ if (pi == null)
+ {
+ throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ pi.SetValue(this.target, value, args);
+ }
+ else
+ {
+ object[] pass = new object[(args?.Length ?? 0) + 1];
+ pass[0] = value;
+ args?.CopyTo(pass, 1);
+ this.InvokeHelper(name, bindingFlags | BindingFlags.SetProperty, pass, null);
+ }
+ }
+
+ ///
+ /// Validate access string
+ ///
+ /// access string
+ private static void ValidateAccessString(string access)
+ {
+ if (access.Length == 0)
+ {
+ throw new ArgumentException("Access string has invalid syntax.");
+ }
+
+ string[] arr = access.Split('.');
+ foreach (string str in arr)
+ {
+ if ((str.Length == 0) || (str.IndexOfAny(new char[] { ' ', '\t', '\n' }) != -1))
+ {
+ throw new ArgumentException("Access string has invalid syntax.");
+ }
+ }
+ }
+
+ ///
+ /// Invokes the memeber
+ ///
+ /// Name of the member
+ /// Additional attributes
+ /// Arguments for the invocation
+ /// Culture
+ /// Result of the invocation
+ private object InvokeHelper(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture)
+ {
+ Debug.Assert(this.target != null, "Internal Error: Null reference is returned for internal object");
+
+ // Invoke the actual Method
+ try
+ {
+ return this.originalType.InvokeMember(name, bindingFlags, null, this.target, args, culture);
+ }
+ catch (TargetInvocationException e)
+ {
+ Debug.Assert(e.InnerException != null, "Inner exception should not be null.");
+ if (e.InnerException != null)
+ {
+ throw e.InnerException;
+ }
+
+ throw;
+ }
+ }
+
+ private void ConstructFrom(object obj)
+ {
+ this.target = obj;
+ this.originalType = obj.GetType();
+ }
+
+ private void BuildGenericMethodCacheForType(Type t)
+ {
+ Debug.Assert(t != null, "type should not be null.");
+ this.methodCache = new Dictionary>();
+
+ MethodInfo[] members = t.GetMethods(BindToEveryThing);
+ LinkedList listByName; // automatically initialized to null
+
+ foreach (MethodInfo member in members)
+ {
+ if (member.IsGenericMethod || member.IsGenericMethodDefinition)
+ {
+ if (!this.GenericMethodCache.TryGetValue(member.Name, out listByName))
+ {
+ listByName = new LinkedList();
+ this.GenericMethodCache.Add(member.Name, listByName);
+ }
+
+ Debug.Assert(listByName != null, "list should not be null.");
+ listByName.AddLast(member);
+ }
+ }
+ }
+
+ ///
+ /// Extracts the most appropriate generic method signature from the current private type.
+ ///
+ /// The name of the method in which to search the signature cache.
+ /// An array of types corresponding to the types of the parameters in which to search.
+ /// An array of types corresponding to the types of the generic arguments.
+ /// to further filter the method signatures.
+ /// Modifiers for parameters.
+ /// A methodinfo instance.
+ private MethodInfo GetGenericMethodFromCache(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(methodName), "Invalid method name.");
+ Debug.Assert(parameterTypes != null, "Invalid parameter type array.");
+ Debug.Assert(typeArguments != null, "Invalid type arguments array.");
+
+ // Build a preliminary list of method candidates that contain roughly the same signature.
+ var methodCandidates = this.GetMethodCandidates(methodName, parameterTypes, typeArguments, bindingFlags, modifiers);
+
+ // Search of ambiguous methods (methods with the same signature).
+ MethodInfo[] finalCandidates = new MethodInfo[methodCandidates.Count];
+ methodCandidates.CopyTo(finalCandidates, 0);
+
+ if ((parameterTypes != null) && (parameterTypes.Length == 0))
+ {
+ for (int i = 0; i < finalCandidates.Length; i++)
+ {
+ MethodInfo methodInfo = finalCandidates[i];
+
+ if (!RuntimeTypeHelper.CompareMethodSigAndName(methodInfo, finalCandidates[0]))
+ {
+ throw new AmbiguousMatchException();
+ }
+ }
+
+ // All the methods have the exact same name and sig so return the most derived one.
+ return RuntimeTypeHelper.FindMostDerivedNewSlotMeth(finalCandidates, finalCandidates.Length) as MethodInfo;
+ }
+
+ // Now that we have a preliminary list of candidates, select the most appropriate one.
+ return RuntimeTypeHelper.SelectMethod(bindingFlags, finalCandidates, parameterTypes, modifiers) as MethodInfo;
+ }
+
+ private LinkedList GetMethodCandidates(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(methodName), "methodName should not be null.");
+ Debug.Assert(parameterTypes != null, "parameterTypes should not be null.");
+ Debug.Assert(typeArguments != null, "typeArguments should not be null.");
+
+ LinkedList methodCandidates = new LinkedList();
+ LinkedList methods = null;
+
+ if (!this.GenericMethodCache.TryGetValue(methodName, out methods))
+ {
+ return methodCandidates;
+ }
+
+ Debug.Assert(methods != null, "methods should not be null.");
+
+ foreach (MethodInfo candidate in methods)
+ {
+ bool paramMatch = true;
+ ParameterInfo[] candidateParams = null;
+ Type[] genericArgs = candidate.GetGenericArguments();
+ Type sourceParameterType = null;
+
+ if (genericArgs.Length != typeArguments.Length)
+ {
+ continue;
+ }
+
+ // Since we can't just get the correct MethodInfo from Reflection,
+ // we will just match the number of parameters, their order, and their type
+ var methodCandidate = candidate;
+ candidateParams = methodCandidate.GetParameters();
+
+ if (candidateParams.Length != parameterTypes.Length)
+ {
+ continue;
+ }
+
+ // Exact binding
+ if ((bindingFlags & BindingFlags.ExactBinding) != 0)
+ {
+ int i = 0;
+
+ foreach (ParameterInfo candidateParam in candidateParams)
+ {
+ sourceParameterType = parameterTypes[i++];
+
+ if (candidateParam.ParameterType.ContainsGenericParameters)
+ {
+ // Since we have a generic parameter here, just make sure the IsArray matches.
+ if (candidateParam.ParameterType.IsArray != sourceParameterType.IsArray)
+ {
+ paramMatch = false;
+ break;
+ }
+ }
+ else
+ {
+ if (candidateParam.ParameterType != sourceParameterType)
+ {
+ paramMatch = false;
+ break;
+ }
+ }
+ }
+
+ if (paramMatch)
+ {
+ methodCandidates.AddLast(methodCandidate);
+ continue;
+ }
+ }
+ else
+ {
+ methodCandidates.AddLast(methodCandidate);
+ }
+ }
+
+ return methodCandidates;
+ }
+ }
+
+ ///
+ /// This class represents a private class for the Private Accessor functionality.
+ ///
+ public class PrivateType
+ {
+ ///
+ /// Binds to everything
+ ///
+ private const BindingFlags BindToEveryThing = BindingFlags.Default
+ | BindingFlags.NonPublic | BindingFlags.Instance
+ | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
+
+ ///
+ /// The wrapped type.
+ ///
+ private Type type;
+
+ ///
+ /// Initializes a new instance of the class that contains the private type.
+ ///
+ /// Assembly name
+ /// fully qualified name of the
+ public PrivateType(string assemblyName, string typeName)
+ {
+ Assembly asm = Assembly.Load(assemblyName);
+
+ this.type = asm.GetType(typeName, true);
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains
+ /// the private type from the type object
+ ///
+ /// The wrapped Type to create.
+ public PrivateType(Type type)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException("type");
+ }
+
+ this.type = type;
+ }
+
+ ///
+ /// Gets the referenced type
+ ///
+ public Type ReferencedType => this.type;
+
+ ///
+ /// Invokes static member
+ ///
+ /// Name of the member to InvokeHelper
+ /// Arguements to the invoction
+ /// Result of invocation
+ public object InvokeStatic(string name, params object[] args)
+ {
+ return this.InvokeStatic(name, null, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes static member
+ ///
+ /// Name of the member to InvokeHelper
+ /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invoction
+ /// Result of invocation
+ public object InvokeStatic(string name, Type[] parameterTypes, object[] args)
+ {
+ return this.InvokeStatic(name, parameterTypes, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes static member
+ ///
+ /// Name of the member to InvokeHelper
+ /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invoction
+ /// An array of types corresponding to the types of the generic arguments.
+ /// Result of invocation
+ public object InvokeStatic(string name, Type[] parameterTypes, object[] args, Type[] typeArguments)
+ {
+ return this.InvokeStatic(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Arguements to the invocation
+ /// Culture
+ /// Result of invocation
+ public object InvokeStatic(string name, object[] args, CultureInfo culture)
+ {
+ return this.InvokeStatic(name, null, args, culture);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invocation
+ /// Culture info
+ /// Result of invocation
+ public object InvokeStatic(string name, Type[] parameterTypes, object[] args, CultureInfo culture)
+ {
+ return this.InvokeStatic(name, BindingFlags.InvokeMethod, parameterTypes, args, culture);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// Arguements to the invocation
+ /// Result of invocation
+ public object InvokeStatic(string name, BindingFlags bindingFlags, params object[] args)
+ {
+ return this.InvokeStatic(name, bindingFlags, null, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invocation
+ /// Result of invocation
+ public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args)
+ {
+ return this.InvokeStatic(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// Arguements to the invocation
+ /// Culture
+ /// Result of invocation
+ public object InvokeStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture)
+ {
+ return this.InvokeStatic(name, bindingFlags, null, args, culture);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invocation
+ /// Culture
+ /// Result of invocation
+ public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture)
+ {
+ return this.InvokeStatic(name, bindingFlags, parameterTypes, args, culture, null);
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke
+ /// Arguements to the invocation
+ /// Culture
+ /// An array of types corresponding to the types of the generic arguments.
+ /// Result of invocation
+ public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments)
+ {
+ if (parameterTypes != null)
+ {
+ MethodInfo member = this.type.GetMethod(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, parameterTypes, null);
+ if (member == null)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ try
+ {
+ if (member.IsGenericMethodDefinition)
+ {
+ MethodInfo constructed = member.MakeGenericMethod(typeArguments);
+ return constructed.Invoke(null, bindingFlags, null, args, culture);
+ }
+ else
+ {
+ return member.Invoke(null, bindingFlags, null, args, culture);
+ }
+ }
+ catch (TargetInvocationException e)
+ {
+ Debug.Assert(e.InnerException != null, "Inner Exception should not be null.");
+ if (e.InnerException != null)
+ {
+ throw e.InnerException;
+ }
+
+ throw;
+ }
+ }
+ else
+ {
+ return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.InvokeMethod, args, culture);
+ }
+ }
+
+ ///
+ /// Gets the element in static array
+ ///
+ /// Name of the array
+ ///
+ /// A one-dimensional array of 32-bit integers that represent the indexes specifying
+ /// the position of the element to get. For instance, to access a[10][11] the indices would be {10,11}
+ ///
+ /// element at the specified location
+ public object GetStaticArrayElement(string name, params int[] indices)
+ {
+ return this.GetStaticArrayElement(name, BindToEveryThing, indices);
+ }
+
+ ///
+ /// Sets the memeber of the static array
+ ///
+ /// Name of the array
+ /// value to set
+ ///
+ /// A one-dimensional array of 32-bit integers that represent the indexes specifying
+ /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11}
+ ///
+ public void SetStaticArrayElement(string name, object value, params int[] indices)
+ {
+ this.SetStaticArrayElement(name, BindToEveryThing, value, indices);
+ }
+
+ ///
+ /// Gets the element in satatic array
+ ///
+ /// Name of the array
+ /// Additional InvokeHelper attributes
+ ///
+ /// A one-dimensional array of 32-bit integers that represent the indexes specifying
+ /// the position of the element to get. For instance, to access a[10][11] the array would be {10,11}
+ ///
+ /// element at the spcified location
+ public object GetStaticArrayElement(string name, BindingFlags bindingFlags, params int[] indices)
+ {
+ Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture);
+ return arr.GetValue(indices);
+ }
+
+ ///
+ /// Sets the memeber of the static array
+ ///
+ /// Name of the array
+ /// Additional InvokeHelper attributes
+ /// value to set
+ ///
+ /// A one-dimensional array of 32-bit integers that represent the indexes specifying
+ /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11}
+ ///
+ public void SetStaticArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices)
+ {
+ Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture);
+ arr.SetValue(value, indices);
+ }
+
+ ///
+ /// Gets the static field
+ ///
+ /// Name of the field
+ /// The static field.
+ public object GetStaticField(string name)
+ {
+ return this.GetStaticField(name, BindToEveryThing);
+ }
+
+ ///
+ /// Sets the static field
+ ///
+ /// Name of the field
+ /// Arguement to the invocation
+ public void SetStaticField(string name, object value)
+ {
+ this.SetStaticField(name, BindToEveryThing, value);
+ }
+
+ ///
+ /// Gets the static field using specified InvokeHelper attributes
+ ///
+ /// Name of the field
+ /// Additional invocation attributes
+ /// The static field.
+ public object GetStaticField(string name, BindingFlags bindingFlags)
+ {
+ return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Sets the static field using binding attributes
+ ///
+ /// Name of the field
+ /// Additional InvokeHelper attributes
+ /// Arguement to the invocation
+ public void SetStaticField(string name, BindingFlags bindingFlags, object value)
+ {
+ this.InvokeHelperStatic(name, BindingFlags.SetField | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Gets the static field or property
+ ///
+ /// Name of the field or property
+ /// The static field or property.
+ public object GetStaticFieldOrProperty(string name)
+ {
+ return this.GetStaticFieldOrProperty(name, BindToEveryThing);
+ }
+
+ ///
+ /// Sets the static field or property
+ ///
+ /// Name of the field or property
+ /// Value to be set to field or property
+ public void SetStaticFieldOrProperty(string name, object value)
+ {
+ this.SetStaticFieldOrProperty(name, BindToEveryThing, value);
+ }
+
+ ///
+ /// Gets the static field or property using specified InvokeHelper attributes
+ ///
+ /// Name of the field or property
+ /// Additional invocation attributes
+ /// The static field or property.
+ public object GetStaticFieldOrProperty(string name, BindingFlags bindingFlags)
+ {
+ return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Sets the static field or property using binding attributes
+ ///
+ /// Name of the field or property
+ /// Additional invocation attributes
+ /// Value to be set to field or property
+ public void SetStaticFieldOrProperty(string name, BindingFlags bindingFlags, object value)
+ {
+ this.InvokeHelperStatic(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Gets the static property
+ ///
+ /// Name of the field or property
+ /// Arguements to the invocation
+ /// The static property.
+ public object GetStaticProperty(string name, params object[] args)
+ {
+ return this.GetStaticProperty(name, BindToEveryThing, args);
+ }
+
+ ///
+ /// Sets the static property
+ ///
+ /// Name of the property
+ /// Value to be set to field or property
+ /// Arguments to pass to the member to invoke.
+ public void SetStaticProperty(string name, object value, params object[] args)
+ {
+ this.SetStaticProperty(name, BindToEveryThing, value, null, args);
+ }
+
+ ///
+ /// Sets the static property
+ ///
+ /// Name of the property
+ /// Value to be set to field or property
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ public void SetStaticProperty(string name, object value, Type[] parameterTypes, object[] args)
+ {
+ this.SetStaticProperty(name, BindingFlags.SetProperty, value, parameterTypes, args);
+ }
+
+ ///
+ /// Gets the static property
+ ///
+ /// Name of the property
+ /// Additional invocation attributes.
+ /// Arguments to pass to the member to invoke.
+ /// The static property.
+ public object GetStaticProperty(string name, BindingFlags bindingFlags, params object[] args)
+ {
+ return this.GetStaticProperty(name, BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, args);
+ }
+
+ ///
+ /// Gets the static property
+ ///
+ /// Name of the property
+ /// Additional invocation attributes.
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ /// The static property.
+ public object GetStaticProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args)
+ {
+ if (parameterTypes != null)
+ {
+ PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null);
+ if (pi == null)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ return pi.GetValue(null, args);
+ }
+ else
+ {
+ return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.GetProperty, args, null);
+ }
+ }
+
+ ///
+ /// Sets the static property
+ ///
+ /// Name of the property
+ /// Additional invocation attributes.
+ /// Value to be set to field or property
+ /// Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.
+ public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, params object[] args)
+ {
+ this.SetStaticProperty(name, bindingFlags, value, null, args);
+ }
+
+ ///
+ /// Sets the static property
+ ///
+ /// Name of the property
+ /// Additional invocation attributes.
+ /// Value to be set to field or property
+ /// An array of objects representing the number, order, and type of the parameters for the indexed property.
+ /// Arguments to pass to the member to invoke.
+ public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args)
+ {
+ if (parameterTypes != null)
+ {
+ PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null);
+ if (pi == null)
+ {
+ throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name));
+ }
+
+ pi.SetValue(null, value, args);
+ }
+ else
+ {
+ object[] pass = new object[(args?.Length ?? 0) + 1];
+ pass[0] = value;
+ args?.CopyTo(pass, 1);
+ this.InvokeHelperStatic(name, bindingFlags | BindingFlags.SetProperty, pass, null);
+ }
+ }
+
+ ///
+ /// Invokes the static method
+ ///
+ /// Name of the member
+ /// Additional invocation attributes
+ /// Arguements to the invocation
+ /// Culture
+ /// Result of invocation
+ private object InvokeHelperStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture)
+ {
+ try
+ {
+ return this.type.InvokeMember(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, null, args, culture);
+ }
+ catch (TargetInvocationException e)
+ {
+ Debug.Assert(e.InnerException != null, "Inner Exception should not be null.");
+ if (e.InnerException != null)
+ {
+ throw e.InnerException;
+ }
+
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Provides method signature discovery for generic methods.
+ ///
+ internal class RuntimeTypeHelper
+ {
+ ///
+ /// Compares the method signatures of these two methods.
+ ///
+ /// Method1
+ /// Method2
+ /// True if they are similiar.
+ internal static bool CompareMethodSigAndName(MethodBase m1, MethodBase m2)
+ {
+ ParameterInfo[] params1 = m1.GetParameters();
+ ParameterInfo[] params2 = m2.GetParameters();
+
+ if (params1.Length != params2.Length)
+ {
+ return false;
+ }
+
+ int numParams = params1.Length;
+ for (int i = 0; i < numParams; i++)
+ {
+ if (params1[i].ParameterType != params2[i].ParameterType)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Gets the hierarchy depth from the base type of the provided type.
+ ///
+ /// The type.
+ /// The depth.
+ internal static int GetHierarchyDepth(Type t)
+ {
+ int depth = 0;
+
+ Type currentType = t;
+ do
+ {
+ depth++;
+ currentType = currentType.BaseType;
+ }
+ while (currentType != null);
+
+ return depth;
+ }
+
+ ///
+ /// Finds most dervied type with the provided information.
+ ///
+ /// Candidate matches.
+ /// Number of matches.
+ /// The most derived method.
+ internal static MethodBase FindMostDerivedNewSlotMeth(MethodBase[] match, int cMatches)
+ {
+ int deepestHierarchy = 0;
+ MethodBase methWithDeepestHierarchy = null;
+
+ for (int i = 0; i < cMatches; i++)
+ {
+ // Calculate the depth of the hierarchy of the declaring type of the
+ // current method.
+ int currentHierarchyDepth = GetHierarchyDepth(match[i].DeclaringType);
+
+ // Two methods with the same hierarchy depth are not allowed. This would
+ // mean that there are 2 methods with the same name and sig on a given type
+ // which is not allowed, unless one of them is vararg...
+ if (currentHierarchyDepth == deepestHierarchy)
+ {
+ if (methWithDeepestHierarchy != null)
+ {
+ Debug.Assert(
+ methWithDeepestHierarchy != null && ((match[i].CallingConvention & CallingConventions.VarArgs)
+ | (methWithDeepestHierarchy.CallingConvention & CallingConventions.VarArgs)) != 0,
+ "Calling conventions: " + match[i].CallingConvention + " - " + methWithDeepestHierarchy.CallingConvention);
+ }
+
+ throw new AmbiguousMatchException();
+ }
+
+ // Check to see if this method is on the most derived class.
+ if (currentHierarchyDepth > deepestHierarchy)
+ {
+ deepestHierarchy = currentHierarchyDepth;
+ methWithDeepestHierarchy = match[i];
+ }
+ }
+
+ return methWithDeepestHierarchy;
+ }
+
+ ///
+ /// Given a set of methods that match the base criteria, select a method based
+ /// upon an array of types. This method should return null if no method matches
+ /// the criteria.
+ ///
+ /// Binding specification.
+ /// Candidate matches
+ /// Types
+ /// Parameter modifiers.
+ /// Matching method. Null if none matches.
+ internal static MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ int i;
+ int j;
+
+ Type[] realTypes = new Type[types.Length];
+ for (i = 0; i < types.Length; i++)
+ {
+ realTypes[i] = types[i].UnderlyingSystemType;
+ }
+
+ types = realTypes;
+
+ // If there are no methods to match to, then return null, indicating that no method
+ // matches the criteria
+ if (match.Length == 0)
+ {
+ return null;
+ }
+
+ // Find all the methods that can be described by the types parameter.
+ // Remove all of them that cannot.
+ int curIdx = 0;
+ for (i = 0; i < match.Length; i++)
+ {
+ ParameterInfo[] par = match[i].GetParameters();
+ if (par.Length != types.Length)
+ {
+ continue;
+ }
+
+ for (j = 0; j < types.Length; j++)
+ {
+ Type pCls = par[j].ParameterType;
+
+ if (pCls.ContainsGenericParameters)
+ {
+ if (pCls.IsArray != types[j].IsArray)
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (pCls == types[j])
+ {
+ continue;
+ }
+
+ if (pCls == typeof(object))
+ {
+ continue;
+ }
+ else
+ {
+ if (!pCls.IsAssignableFrom(types[j]))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if (j == types.Length)
+ {
+ match[curIdx++] = match[i];
+ }
+ }
+
+ if (curIdx == 0)
+ {
+ return null;
+ }
+
+ if (curIdx == 1)
+ {
+ return match[0];
+ }
+
+ // Walk all of the methods looking the most specific method to invoke
+ int currentMin = 0;
+ bool ambig = false;
+ int[] paramOrder = new int[types.Length];
+ for (i = 0; i < types.Length; i++)
+ {
+ paramOrder[i] = i;
+ }
+
+ for (i = 1; i < curIdx; i++)
+ {
+ int newMin = FindMostSpecificMethod(match[currentMin], paramOrder, null, match[i], paramOrder, null, types, null);
+ if (newMin == 0)
+ {
+ ambig = true;
+ }
+ else
+ {
+ if (newMin == 2)
+ {
+ currentMin = i;
+ ambig = false;
+ currentMin = i;
+ }
+ }
+ }
+
+ if (ambig)
+ {
+ throw new AmbiguousMatchException();
+ }
+
+ return match[currentMin];
+ }
+
+ ///
+ /// Finds the most specific method in the two methods provided.
+ ///
+ /// Method 1
+ /// Parameter order for Method 1
+ /// Paramter array type.
+ /// Method 2
+ /// Parameter order for Method 2
+ /// >Paramter array type.
+ /// Types to search in.
+ /// Args.
+ /// An int representing the match.
+ internal static int FindMostSpecificMethod(
+ MethodBase m1,
+ int[] paramOrder1,
+ Type paramArrayType1,
+ MethodBase m2,
+ int[] paramOrder2,
+ Type paramArrayType2,
+ Type[] types,
+ object[] args)
+ {
+ // Find the most specific method based on the parameters.
+ int res = FindMostSpecific(
+ m1.GetParameters(),
+ paramOrder1,
+ paramArrayType1,
+ m2.GetParameters(),
+ paramOrder2,
+ paramArrayType2,
+ types,
+ args);
+
+ // If the match was not ambiguous then return the result.
+ if (res != 0)
+ {
+ return res;
+ }
+
+ // Check to see if the methods have the exact same name and signature.
+ if (CompareMethodSigAndName(m1, m2))
+ {
+ // Determine the depth of the declaring types for both methods.
+ int hierarchyDepth1 = GetHierarchyDepth(m1.DeclaringType);
+ int hierarchyDepth2 = GetHierarchyDepth(m2.DeclaringType);
+
+ // The most derived method is the most specific one.
+ if (hierarchyDepth1 == hierarchyDepth2)
+ {
+ return 0;
+ }
+ else if (hierarchyDepth1 < hierarchyDepth2)
+ {
+ return 2;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ // The match is ambiguous.
+ return 0;
+ }
+
+ ///
+ /// Finds the most specific method in the two methods provided.
+ ///
+ /// Method 1
+ /// Parameter order for Method 1
+ /// Paramter array type.
+ /// Method 2
+ /// Parameter order for Method 2
+ /// >Paramter array type.
+ /// Types to search in.
+ /// Args.
+ /// An int representing the match.
+ internal static int FindMostSpecific(
+ ParameterInfo[] p1,
+ int[] paramOrder1,
+ Type paramArrayType1,
+ ParameterInfo[] p2,
+ int[] paramOrder2,
+ Type paramArrayType2,
+ Type[] types,
+ object[] args)
+ {
+ // A method using params is always less specific than one not using params
+ if (paramArrayType1 != null && paramArrayType2 == null)
+ {
+ return 2;
+ }
+
+ if (paramArrayType2 != null && paramArrayType1 == null)
+ {
+ return 1;
+ }
+
+ bool p1Less = false;
+ bool p2Less = false;
+
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (args != null && args[i] == Type.Missing)
+ {
+ continue;
+ }
+
+ Type c1, c2;
+
+ // If a param array is present, then either
+ // the user re-ordered the parameters in which case
+ // the argument to the param array is either an array
+ // in which case the params is conceptually ignored and so paramArrayType1 == null
+ // or the argument to the param array is a single element
+ // in which case paramOrder[i] == p1.Length - 1 for that element
+ // or the user did not re-order the parameters in which case
+ // the paramOrder array could contain indexes larger than p.Length - 1
+ //// so any index >= p.Length - 1 is being put in the param array
+
+ if (paramArrayType1 != null && paramOrder1[i] >= p1.Length - 1)
+ {
+ c1 = paramArrayType1;
+ }
+ else
+ {
+ c1 = p1[paramOrder1[i]].ParameterType;
+ }
+
+ if (paramArrayType2 != null && paramOrder2[i] >= p2.Length - 1)
+ {
+ c2 = paramArrayType2;
+ }
+ else
+ {
+ c2 = p2[paramOrder2[i]].ParameterType;
+ }
+
+ if (c1 == c2)
+ {
+ continue;
+ }
+
+ if (c1.ContainsGenericParameters || c2.ContainsGenericParameters)
+ {
+ continue;
+ }
+
+ switch (FindMostSpecificType(c1, c2, types[i]))
+ {
+ case 0:
+ return 0;
+ case 1:
+ p1Less = true;
+ break;
+ case 2:
+ p2Less = true;
+ break;
+ }
+ }
+
+ // Two way p1Less and p2Less can be equal. All the arguments are the
+ // same they both equal false, otherwise there were things that both
+ // were the most specific type on....
+ if (p1Less == p2Less)
+ {
+ // it's possible that the 2 methods have same sig and default param in which case we match the one
+ // with the same number of args but only if they were exactly the same (that is p1Less and p2Lees are both false)
+ if (!p1Less && p1.Length != p2.Length && args != null)
+ {
+ if (p1.Length == args.Length)
+ {
+ return 1;
+ }
+ else if (p2.Length == args.Length)
+ {
+ return 2;
+ }
+ }
+
+ return 0;
+ }
+ else
+ {
+ return (p1Less == true) ? 1 : 2;
+ }
+ }
+
+ ///
+ /// Finds the most specific type in the two provided.
+ ///
+ /// Type 1
+ /// Type 2
+ /// The defining type
+ /// An int representing the match.
+ internal static int FindMostSpecificType(Type c1, Type c2, Type t)
+ {
+ // If the two types are exact move on...
+ if (c1 == c2)
+ {
+ return 0;
+ }
+
+ if (c1 == t)
+ {
+ return 1;
+ }
+
+ if (c2 == t)
+ {
+ return 2;
+ }
+
+ bool c1FromC2;
+ bool c2FromC1;
+
+ if (c1.IsByRef || c2.IsByRef)
+ {
+ if (c1.IsByRef && c2.IsByRef)
+ {
+ c1 = c1.GetElementType();
+ c2 = c2.GetElementType();
+ }
+ else if (c1.IsByRef)
+ {
+ if (c1.GetElementType() == c2)
+ {
+ return 2;
+ }
+
+ c1 = c1.GetElementType();
+ }
+ else
+ {
+ if (c2.GetElementType() == c1)
+ {
+ return 1;
+ }
+
+ c2 = c2.GetElementType();
+ }
+ }
+
+ if (c1.IsPrimitive && c2.IsPrimitive)
+ {
+ c1FromC2 = true;
+ c2FromC1 = true;
+ }
+ else
+ {
+ c1FromC2 = c1.IsAssignableFrom(c2);
+ c2FromC1 = c2.IsAssignableFrom(c1);
+ }
+
+ if (c1FromC2 == c2FromC1)
+ {
+ return 0;
+ }
+
+ if (c1FromC2)
+ {
+ return 2;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/API.Tests/ParserTest.cs b/API.Tests/ParserTest.cs
index 897bc99a8..4a29b0a71 100644
--- a/API.Tests/ParserTest.cs
+++ b/API.Tests/ParserTest.cs
@@ -47,7 +47,8 @@ namespace API.Tests
[InlineData("Yumekui_Merry_v01_c01[Bakayarou-Kuu].rar", "1")]
[InlineData("Yumekui-Merry_DKThias_Chapter11v2.zip", "0")]
[InlineData("Itoshi no Karin - c001-006x1 (v01) [Renzokusei Scans]", "1")]
-
+ [InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12", "0")]
+ [InlineData("VanDread-v01-c001[MD].zip", "1")]
public void ParseVolumeTest(string filename, string expected)
{
Assert.Equal(expected, ParseVolume(filename));
@@ -95,6 +96,9 @@ namespace API.Tests
[InlineData("Tonikaku Kawaii Vol-1 (Ch 01-08)", "Tonikaku Kawaii")]
[InlineData("Tonikaku Kawaii (Ch 59-67) (Ongoing)", "Tonikaku Kawaii")]
[InlineData("7thGARDEN v01 (2016) (Digital) (danke).cbz", "7thGARDEN")]
+ [InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12", "Kedouin Makoto - Corpse Party Musume")]
+ [InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 09", "Kedouin Makoto - Corpse Party Musume")]
+ [InlineData("Goblin Slayer Side Story - Year One 025.5", "Goblin Slayer Side Story - Year One")]
public void ParseSeriesTest(string filename, string expected)
{
Assert.Equal(expected, ParseSeries(filename));
@@ -130,7 +134,10 @@ namespace API.Tests
[InlineData("Black Bullet - v4 c20.5 [batoto]", "20.5")]
[InlineData("Itoshi no Karin - c001-006x1 (v01) [Renzokusei Scans]", "1-6")]
[InlineData("APOSIMZ 040 (2020) (Digital) (danke-Empire).cbz", "40")]
+ [InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12", "12")]
[InlineData("Vol 1", "0")]
+ [InlineData("VanDread-v01-c001[MD].zip", "1")]
+ [InlineData("Goblin Slayer Side Story - Year One 025.5", "25.5")]
//[InlineData("[Tempus Edax Rerum] Epigraph of the Closed Curve - Chapter 6.zip", "6")]
public void ParseChaptersTest(string filename, string expected)
{
@@ -278,6 +285,14 @@ namespace API.Tests
FullFilePath = filepath
});
+ filepath = @"E:\Manga\Corpse Party Musume\Kedouin Makoto - Corpse Party Musume, Chapter 09.cbz";
+ expected.Add(filepath, new ParserInfo
+ {
+ Series = "Corpse Party Musume - Coprse Party", Volumes = "0", Edition = "",
+ Chapters = "9", Filename = "Kedouin Makoto - Corpse Party Musume, Chapter 09.cbz", Format = MangaFormat.Archive,
+ FullFilePath = filepath
+ });
+
diff --git a/API.Tests/Services/DirectoryServiceTests.cs b/API.Tests/Services/DirectoryServiceTests.cs
index dea8e47fe..567c7e6a9 100644
--- a/API.Tests/Services/DirectoryServiceTests.cs
+++ b/API.Tests/Services/DirectoryServiceTests.cs
@@ -1,7 +1,32 @@
-namespace API.Tests.Services
+using API.Interfaces;
+using API.Services;
+using Microsoft.Extensions.Logging;
+using NSubstitute;
+using Xunit;
+
+namespace API.Tests.Services
{
+
public class DirectoryServiceTests
{
-
+ private readonly DirectoryService _directoryService;
+ private readonly ILogger _logger = Substitute.For>();
+
+ public DirectoryServiceTests()
+ {
+ _directoryService = new DirectoryService(_logger);
+ }
+
+ [Fact]
+ public void GetFiles_Test()
+ {
+ //_directoryService.GetFiles()
+ }
+
+ [Fact]
+ public void ListDirectory_Test()
+ {
+
+ }
}
}
\ No newline at end of file
diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs
index eeb7ae560..70b4d7e83 100644
--- a/API.Tests/Services/ScannerServiceTests.cs
+++ b/API.Tests/Services/ScannerServiceTests.cs
@@ -1,27 +1,37 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using API.Entities;
+using API.Entities.Enums;
using API.Interfaces;
+using API.Interfaces.Services;
+using API.Parser;
using API.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using Xunit;
+using Xunit.Abstractions;
namespace API.Tests.Services
{
public class ScannerServiceTests
{
+ private readonly ITestOutputHelper _testOutputHelper;
private readonly ScannerService _scannerService;
private readonly ILogger _logger = Substitute.For>();
private readonly IUnitOfWork _unitOfWork = Substitute.For();
private readonly IArchiveService _archiveService = Substitute.For();
- //private readonly IDirectoryService _directoryService = Substitute.For();
+ private readonly IMetadataService _metadataService;
+ private readonly ILogger _metadataLogger = Substitute.For>();
private Library _libraryMock;
- public ScannerServiceTests()
+ public ScannerServiceTests(ITestOutputHelper testOutputHelper)
{
- _scannerService = new ScannerService(_unitOfWork, _logger, _archiveService);
+ _testOutputHelper = testOutputHelper;
+ _scannerService = new ScannerService(_unitOfWork, _logger, _archiveService, _metadataService);
+ _metadataService= Substitute.For(_unitOfWork, _metadataLogger, _archiveService);
_libraryMock = new Library()
{
Id = 1,
@@ -59,6 +69,7 @@ namespace API.Tests.Services
new Series() {Id = 4, Name = "Akame Ga Kill"},
};
Assert.Equal(_libraryMock.Series.ElementAt(0).Id, ScannerService.ExistingOrDefault(_libraryMock, allSeries, "Darker Than Black").Id);
+ Assert.Equal(_libraryMock.Series.ElementAt(0).Id, ScannerService.ExistingOrDefault(_libraryMock, allSeries, "Darker than Black").Id);
}
[Fact]
@@ -85,31 +96,23 @@ namespace API.Tests.Services
Assert.Null(ScannerService.ExistingOrDefault(_libraryMock, allSeries, "Non existing series"));
}
- // [Fact]
- // public void ScanLibrary_Should_Skip()
- // {
- //
- Library lib = new Library()
+ [Fact]
+ public void Should_CreateSeries_Test()
{
- Id = 1,
- Name = "Darker Than Black",
- Folders = new List()
+ var allSeries = new List();
+ var parsedSeries = new Dictionary>();
+
+ parsedSeries.Add("Darker Than Black", new List()
{
- new FolderPath()
- {
- Id = 1,
- LastScanned = DateTime.Now,
- LibraryId = 1,
- Path = "E:/Manga"
- }
- },
- LastModified = DateTime.Now
- };
- //
- // _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1).Returns(lib);
- //
- // _scannerService.ScanLibrary(1, false);
- // }
-
+ new ParserInfo() {Chapters = "0", Filename = "Something.cbz", Format = MangaFormat.Archive, FullFilePath = "E:/Manga/Something.cbz", Series = "Darker Than Black", Volumes = "1"},
+ new ParserInfo() {Chapters = "0", Filename = "Something.cbz", Format = MangaFormat.Archive, FullFilePath = "E:/Manga/Something.cbz", Series = "Darker than Black", Volumes = "2"}
+ });
+
+ _scannerService.UpsertSeries(_libraryMock, parsedSeries, allSeries);
+
+ Assert.Equal(1, _libraryMock.Series.Count);
+ Assert.Equal(2, _libraryMock.Series.ElementAt(0).Volumes.Count);
+ _testOutputHelper.WriteLine(_libraryMock.ToString());
+ }
}
}
\ No newline at end of file
diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs
index 04c38f75b..ddc9a3b61 100644
--- a/API/Entities/Series.cs
+++ b/API/Entities/Series.cs
@@ -36,7 +36,7 @@ namespace API.Entities
public int Pages { get; set; }
// Relationships
- public ICollection Volumes { get; set; }
+ public List Volumes { get; set; }
public Library Library { get; set; }
public int LibraryId { get; set; }
}
diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs
index 06ce2df99..31df9ac80 100644
--- a/API/Services/DirectoryService.cs
+++ b/API/Services/DirectoryService.cs
@@ -91,7 +91,7 @@ namespace API.Services
/// Directory to scan
/// Action to apply on file path
///
- public static int TraverseTreeParallelForEach(string root, Action action)
+ public static int TraverseTreeParallelForEach(string root, Action action, string searchPattern)
{
//Count of files traversed and timer for diagnostic output
var fileCount = 0;
@@ -130,7 +130,7 @@ namespace API.Services
// TODO: In future, we need to take LibraryType into consideration for what extensions to allow (RAW should allow images)
// or we need to move this filtering to another area (Process)
// or we can get all files and put a check in place during Process to abandon files
- files = GetFilesWithCertainExtensions(currentDir, Parser.Parser.MangaFileExtensions)
+ files = GetFilesWithCertainExtensions(currentDir, searchPattern)
.ToArray();
}
catch (UnauthorizedAccessException e) {
diff --git a/API/Services/ScannerService.cs b/API/Services/ScannerService.cs
index a0a9e7689..5ac321321 100644
--- a/API/Services/ScannerService.cs
+++ b/API/Services/ScannerService.cs
@@ -67,7 +67,7 @@ namespace API.Services
_scannedSeries = null;
}
- [DisableConcurrentExecution(timeoutInSeconds: 120)]
+ [DisableConcurrentExecution(timeoutInSeconds: 360)]
public void ScanLibrary(int libraryId, bool forceUpdate)
{
_forceUpdate = forceUpdate;
@@ -105,7 +105,7 @@ namespace API.Services
{
_logger.LogError(exception, $"The file {f} could not be found");
}
- });
+ }, Parser.Parser.MangaFileExtensions);
}
catch (ArgumentException ex) {
_logger.LogError(ex, $"The directory '{folderPath.Path}' does not exist");
@@ -170,17 +170,18 @@ namespace API.Services
{
try
{
+ // TODO: I don't need library here. It will always pull from allSeries
var mangaSeries = ExistingOrDefault(library, allSeries, seriesKey) ?? new Series
{
- Name = seriesKey, // NOTE: Should I apply Title casing here
+ Name = seriesKey,
OriginalName = seriesKey,
NormalizedName = Parser.Parser.Normalize(seriesKey),
SortName = seriesKey,
Summary = ""
};
mangaSeries.NormalizedName = Parser.Parser.Normalize(mangaSeries.Name);
-
-
+
+
UpdateSeries(ref mangaSeries, parsedSeries[seriesKey].ToArray());
if (library.Series.Any(s => Parser.Parser.Normalize(s.Name) == mangaSeries.NormalizedName)) continue;
_logger.LogInformation($"Added series {mangaSeries.Name}");
@@ -215,6 +216,20 @@ namespace API.Services
}
_logger.LogInformation($"Removed {count} series that are no longer on disk");
}
+
+ private void RemoveVolumesNotOnDisk(Series series)
+ {
+ var volumes = series.Volumes.ToList();
+ foreach (var volume in volumes)
+ {
+ var chapters = volume.Chapters;
+ if (!chapters.Any())
+ {
+ series.Volumes.Remove(volume);
+ //chapters.Select(c => c.Files).Any()
+ }
+ }
+ }
///
@@ -260,9 +275,11 @@ namespace API.Services
{
_logger.LogInformation($"Updating entries for {series.Name}. {infos.Length} related files.");
- UpdateVolumes(series, infos);
- series.Pages = series.Volumes.Sum(v => v.Pages);
+ UpdateVolumes(series, infos);
+ RemoveVolumesNotOnDisk(series);
+ series.Pages = series.Volumes.Sum(v => v.Pages);
+
_metadataService.UpdateMetadata(series, _forceUpdate);
_logger.LogDebug($"Created {series.Volumes.Count} volumes on {series.Name}");
}
@@ -352,10 +369,11 @@ namespace API.Services
}
- private void UpdateVolumes(Series series, ParserInfo[] infos)
+ private void UpdateVolumes(Series series, IReadOnlyCollection infos)
{
+ // BUG: If a volume no longer exists, it is not getting deleted.
series.Volumes ??= new List();
- _logger.LogDebug($"Updating Volumes for {series.Name}. {infos.Length} related files.");
+ _logger.LogDebug($"Updating Volumes for {series.Name}. {infos.Count} related files.");
var existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList();
foreach (var info in infos)