<commit_before>﻿//-----------------------------------------------------------------------
// <copyright file="ServiceProviderMethodCaller.cs" company="Marimer LLC">
//     Copyright (c) Marimer LLC. All rights reserved.
//     Website: https://cslanet.com
// </copyright>
// <summary>Dynamically find/invoke methods with DI provided params</summary>
//-----------------------------------------------------------------------
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Csla.Properties;

namespace Csla.Reflection
{
  /// <summary>
  /// Methods to dynamically find/invoke methods
  /// with data portal and DI provided params
  /// </summary>
  public class ServiceProviderMethodCaller : Core.IUseApplicationContext
  {
    private static readonly BindingFlags _bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.DeclaredOnly;
    private static readonly BindingFlags _factoryBindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
    private static readonly ConcurrentDictionary<string, ServiceProviderMethodInfo> _methodCache =
      new ConcurrentDictionary<string, ServiceProviderMethodInfo>();

    /// <summary>
    /// Gets or sets the current ApplicationContext object.
    /// </summary>
    public ApplicationContext ApplicationContext { get; set; }


    /// <summary>
    /// Find a method based on data portal criteria
    /// and providing any remaining parameters with
    /// values from an IServiceProvider
    /// </summary>
    /// <param name="target">Object with methods</param>
    /// <param name="criteria">Data portal criteria values</param>
    public ServiceProviderMethodInfo FindDataPortalMethod<T>(object target, object[] criteria)
      where T : DataPortalOperationAttribute
    {
      if (target == null)
        throw new ArgumentNullException(nameof(target));

      var targetType = target.GetType();
      return FindDataPortalMethod<T>(targetType, criteria);
    }

    /// <summary>
    /// Find a method based on data portal criteria
    /// and providing any remaining parameters with
    /// values from an IServiceProvider
    /// </summary>
    /// <param name="targetType">Type of domain object</param>
    /// <param name="criteria">Data portal criteria values</param>
    /// <param name="throwOnError">Throw exceptions on error</param>
    public ServiceProviderMethodInfo FindDataPortalMethod<T>(Type targetType, object[] criteria, bool throwOnError = true)
      where T : DataPortalOperationAttribute
    {
      if (targetType == null)
        throw new ArgumentNullException(nameof(targetType));

      var typeOfOperation = typeof(T);

      var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria);
      if (_methodCache.TryGetValue(cacheKey, out ServiceProviderMethodInfo cachedMethod))
        if (!throwOnError || cachedMethod != null)
          return cachedMethod;

      var candidates = new List<ScoredMethodInfo>();
      var factoryInfo = Csla.Server.ObjectFactoryAttribute.GetObjectFactoryAttribute(targetType);
      if (factoryInfo != null)
      {
        var factoryType = Csla.Server.FactoryDataPortal.FactoryLoader.GetFactoryType(factoryInfo.FactoryTypeName);
        var ftList = new List<System.Reflection.MethodInfo>();
        var level = 0;
        while (factoryType != null)
        {
          ftList.Clear();
          if (typeOfOperation == typeof(CreateAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.CreateMethodName));
          else if (typeOfOperation == typeof(FetchAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.FetchMethodName));
          else if (typeOfOperation == typeof(DeleteAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.DeleteMethodName));
          else if (typeOfOperation == typeof(ExecuteAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.ExecuteMethodName));
          else if (typeOfOperation == typeof(CreateChildAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == "Child_Create"));
          else
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.UpdateMethodName));
          factoryType = factoryType.BaseType;
          candidates.AddRange(ftList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
          level--;
        }
        if (!candidates.Any() && typeOfOperation == typeof(CreateChildAttribute))
        {
          var ftlist = targetType.GetMethods(_bindingAttr).Where(m => m.Name == "Child_Create");
          candidates.AddRange(ftList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = 0 }));
        }
      }
      else // not using factory types
      {
        var tt = targetType;
        var level = 0;
        while (tt != null)
        {
          var ttList = tt.GetMethods(_bindingAttr).Where(m => m.GetCustomAttributes<T>().Any());
          candidates.AddRange(ttlist.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
          tt = tt.BaseType;
          level--;
        }

        // if no attribute-based methods found, look for legacy methods
        if (!candidates.Any())
        {
          var attributeName = typeOfOperation.Name.Substring(0, typeOfOperation.Name.IndexOf("Attribute"));
          var methodName = attributeName.Contains("Child") ?
              "Child_" + attributeName.Substring(0, attributeName.IndexOf("Child")) :
              "DataPortal_" + attributeName;
          tt = targetType;
          level = 0;
          while (tt != null)
          {
            var ttList = tt.GetMethods(_bindingAttr).Where(m => m.Name == methodName);
            candidates.AddRange(ttList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
            tt = tt.BaseType;
            level--;
          }
        }
      }

      ScoredMethodInfo result = null;

      if (candidates != null && candidates.Any())
      {
        // scan candidate methods for matching criteria parameters
        int criteriaLength = 0;
        if (criteria != null)
          if (criteria.GetType().Equals(typeof(object[])))<commit_msg>Fixed typo on correct line<commit_after>﻿//-----------------------------------------------------------------------
// <copyright file="ServiceProviderMethodCaller.cs" company="Marimer LLC">
//     Copyright (c) Marimer LLC. All rights reserved.
//     Website: https://cslanet.com
// </copyright>
// <summary>Dynamically find/invoke methods with DI provided params</summary>
//-----------------------------------------------------------------------
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Csla.Properties;

namespace Csla.Reflection
{
  /// <summary>
  /// Methods to dynamically find/invoke methods
  /// with data portal and DI provided params
  /// </summary>
  public class ServiceProviderMethodCaller : Core.IUseApplicationContext
  {
    private static readonly BindingFlags _bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.DeclaredOnly;
    private static readonly BindingFlags _factoryBindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
    private static readonly ConcurrentDictionary<string, ServiceProviderMethodInfo> _methodCache =
      new ConcurrentDictionary<string, ServiceProviderMethodInfo>();

    /// <summary>
    /// Gets or sets the current ApplicationContext object.
    /// </summary>
    public ApplicationContext ApplicationContext { get; set; }


    /// <summary>
    /// Find a method based on data portal criteria
    /// and providing any remaining parameters with
    /// values from an IServiceProvider
    /// </summary>
    /// <param name="target">Object with methods</param>
    /// <param name="criteria">Data portal criteria values</param>
    public ServiceProviderMethodInfo FindDataPortalMethod<T>(object target, object[] criteria)
      where T : DataPortalOperationAttribute
    {
      if (target == null)
        throw new ArgumentNullException(nameof(target));

      var targetType = target.GetType();
      return FindDataPortalMethod<T>(targetType, criteria);
    }

    /// <summary>
    /// Find a method based on data portal criteria
    /// and providing any remaining parameters with
    /// values from an IServiceProvider
    /// </summary>
    /// <param name="targetType">Type of domain object</param>
    /// <param name="criteria">Data portal criteria values</param>
    /// <param name="throwOnError">Throw exceptions on error</param>
    public ServiceProviderMethodInfo FindDataPortalMethod<T>(Type targetType, object[] criteria, bool throwOnError = true)
      where T : DataPortalOperationAttribute
    {
      if (targetType == null)
        throw new ArgumentNullException(nameof(targetType));

      var typeOfOperation = typeof(T);

      var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria);
      if (_methodCache.TryGetValue(cacheKey, out ServiceProviderMethodInfo cachedMethod))
        if (!throwOnError || cachedMethod != null)
          return cachedMethod;

      var candidates = new List<ScoredMethodInfo>();
      var factoryInfo = Csla.Server.ObjectFactoryAttribute.GetObjectFactoryAttribute(targetType);
      if (factoryInfo != null)
      {
        var factoryType = Csla.Server.FactoryDataPortal.FactoryLoader.GetFactoryType(factoryInfo.FactoryTypeName);
        var ftList = new List<System.Reflection.MethodInfo>();
        var level = 0;
        while (factoryType != null)
        {
          ftList.Clear();
          if (typeOfOperation == typeof(CreateAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.CreateMethodName));
          else if (typeOfOperation == typeof(FetchAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.FetchMethodName));
          else if (typeOfOperation == typeof(DeleteAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.DeleteMethodName));
          else if (typeOfOperation == typeof(ExecuteAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.ExecuteMethodName));
          else if (typeOfOperation == typeof(CreateChildAttribute))
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == "Child_Create"));
          else
            ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.UpdateMethodName));
          factoryType = factoryType.BaseType;
          candidates.AddRange(ftList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
          level--;
        }
        if (!candidates.Any() && typeOfOperation == typeof(CreateChildAttribute))
        {
          var ftlist = targetType.GetMethods(_bindingAttr).Where(m => m.Name == "Child_Create");
          candidates.AddRange(ftlist.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = 0 }));
        }
      }
      else // not using factory types
      {
        var tt = targetType;
        var level = 0;
        while (tt != null)
        {
          var ttList = tt.GetMethods(_bindingAttr).Where(m => m.GetCustomAttributes<T>().Any());
          candidates.AddRange(ttList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
          tt = tt.BaseType;
          level--;
        }

        // if no attribute-based methods found, look for legacy methods
        if (!candidates.Any())
        {
          var attributeName = typeOfOperation.Name.Substring(0, typeOfOperation.Name.IndexOf("Attribute"));
          var methodName = attributeName.Contains("Child") ?
              "Child_" + attributeName.Substring(0, attributeName.IndexOf("Child")) :
              "DataPortal_" + attributeName;
          tt = targetType;
          level = 0;
          while (tt != null)
          {
            var ttList = tt.GetMethods(_bindingAttr).Where(m => m.Name == methodName);
            candidates.AddRange(ttList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level }));
            tt = tt.BaseType;
            level--;
          }
        }
      }

      ScoredMethodInfo result = null;

      if (candidates != null && candidates.Any())
      {
        // scan candidate methods for matching criteria parameters
        int criteriaLength = 0;
        if (criteria != null)
          if (criteria.GetType().Equals(typeof(object[])))
{'fraction_non_alphanumeric': 0.1302594537015902, 'fraction_numerical': 0.0010652058129802936, 'mean_word_length': 2.7436627741384223, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 89, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6884126', 'n_tokens_mistral': 3344, 'n_tokens_neox': 3297, 'n_words': 807}