DoCli/Objects/Testing/ProxyClassTypeDefinitionBuilder.psm1
using namespace DoFramework.Environment; using namespace DoFramework.Data; using namespace DoFramework.Domain; using namespace DoFramework.Testing; using namespace DoFramework.Services; using namespace System; using namespace System.Text; using namespace System.Reflection; using namespace System.Collections.Generic; <# .SYNOPSIS Class for building proxy class type definitions within the DoFramework environment. .DESCRIPTION The ProxyClassTypeDefinitionBuilder class is designed to dynamically build proxy class types based on the provided type and constructor arguments. It handles importing necessary modules, generating constructors, and overriding methods. #> class ProxyClassTypeDefinitionBuilder { <# .SYNOPSIS Initializes a new instance of the ProxyClassTypeDefinitionBuilder class. .DESCRIPTION Constructor for the ProxyClassTypeDefinitionBuilder class, which sets up the environment and module provider for building proxy class type definitions. #> [IEnvironment] $Environment; [IDataCollectionProvider[ModuleDescriptor, string]] $ModuleProvider; ProxyClassTypeDefinitionBuilder( [IEnvironment] $environment, [IDataCollectionProvider[ModuleDescriptor, string]] $moduleProvider ) { $this.Environment = $environment; $this.ModuleProvider = $moduleProvider; } <# .SYNOPSIS Builds a proxy class type definition based on the provided type and constructor arguments. .DESCRIPTION The Build method generates a proxy class type definition, including importing necessary modules, creating constructors, and overriding methods. #> [Type] Build([Type] $type, [object[]] $constructorArgs) { [MethodInfo[]] $methods = $type.GetMethods(); [StringBuilder] $sb = [StringBuilder]::new(); [List[ModuleDescriptor]] $content = $this.ModuleProvider.Provide(".*"); foreach ($descriptor in $content) { $sb.AppendLine("using module $($this.Environment.ModuleDir)\$($descriptor.Path);"); } [string] $className = "$($type.Name)Proxy"; $sb.AppendLine("class $className : $($this.CleanTypeName($type.FullName)) {"); $sb.AppendLine("[$($this.CleanTypeName([ClassProxy].FullName))] `$Proxy;"); [ConstructorInfo[]] $constructors = $type.GetConstructors(); foreach ($constructor in $constructors) { $this.AppendConstructor($className, $constructor, $sb); } foreach ($method in $methods) { if ((!$method.IsStatic ` -and !$method.IsConstructor ` -and !$method.IsGenericMethod ` -and ($method.IsAbstract -or $method.IsVirtual) ` -and $method.DeclaringType -ne [Object]) ` -or ($method.IsSpecialName ` -and ($method.Name.StartsWith("get_") ` -or $method.Name.StartsWith("set_")))) { $this.AppendMethod($method, $sb); } } $sb.AppendLine("}"); $sb.AppendLine("return [$className]"); [Type] $type = Invoke-Expression -Command $sb.ToString(); return $type; } <# .SYNOPSIS Appends a constructor definition to the proxy class. .DESCRIPTION The AppendConstructor method generates a constructor definition for the proxy class, including the proxy parameter and base class initialization. #> [void] AppendConstructor([string] $className, [ConstructorInfo] $constructor, [StringBuilder] $sb) { $sb.AppendLine("$className("); $sb.Append("[$($this.CleanTypeName([ClassProxy].FullName))] `$proxy"); [StringBuilder] $signatureParams = [StringBuilder]::new(); [StringBuilder] $baseCall = [StringBuilder]::new(); [ParameterInfo[]] $params = $constructor.GetParameters(); for ([int] $i = 0; $i -lt $params.Length; $i++) { $signatureParams.Append(", [$($this.CleanTypeName($params[$i].ParameterType.FullName))] `$arg$i"); $baseCall.Append("`$arg$i"); if ($i -lt $params.Length - 1) { $baseCall.Append(", "); } } $sb.Append($signatureParams.ToString()); $sb.Append(") : base($($baseCall.ToString())) "); $sb.AppendLine("{ `$this.Proxy = `$proxy; }"); } <# .SYNOPSIS Appends a method definition to the proxy class. .DESCRIPTION The AppendMethod method generates a method definition for the proxy class, including the method signature, parameters, and proxy invocation. #> [void] AppendMethod([MethodInfo] $method, [StringBuilder] $sb) { $parameters = $method.GetParameters(); [string] $paramSignatureString = [string]::Empty; [string] $paramDefinitionString = [string]::Empty; [string] $suppliedParamString = [string]::Empty; if ($method.IsSpecialName -and $method.Name.StartsWith("set_")) { $paramSignatureString += "[$($this.CleanTypeName($parameters[0].ParameterType.FullName))] `$value"; $paramDefinitionString += "-value ([$($this.CleanTypeName($parameters[0].ParameterType.FullName))]) "; $suppliedParamString += "`$value"; } else { for ($i = 0; $i -lt $parameters.Length; $i++) { $paramSignatureString += "[$($this.CleanTypeName($parameters[$i].ParameterType.FullName))] `$$($parameters[$i].Name)"; $paramDefinitionString += "-$($parameters[$i].Name) ([$($this.CleanTypeName($parameters[$i].ParameterType.FullName))]) "; $suppliedParamString += "`$$($parameters[$i].Name)"; if ($i -lt $parameters.Length - 1) { $paramSignatureString += ","; $suppliedParamString += ","; } } } $sb.AppendLine("[$($this.CleanTypeName($method.ReturnType.FullName))] $($method.Name)($paramSignatureString) {"); if ($method.ReturnType -eq [Void]) { $sb.AppendLine("`$this.Proxy.Invoke((doing get-methodinfo -methodName $($method.Name) -type (`$this.GetType().BaseType) -parameters (doing read-args $paramDefinitionString)), @( $suppliedParamString ));"); } else { $sb.AppendLine("return `$this.Proxy.Invoke((doing get-methodinfo -methodName $($method.Name) -type (`$this.GetType().BaseType) -parameters (doing read-args $paramDefinitionString)), @( $suppliedParamString ));"); } $sb.AppendLine("}"); } <# .SYNOPSIS Cleans a Type's fullname. .DESCRIPTION Removes extra information relating to a Type's true full name so it presents as if it were written in code E.g. [System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]] becomes [System.Collections.Generic.Dictionary[[System.String],[System.String]]] #> [string] CleanTypeName([string] $typeName) { return $typeName -replace ', [^]]+', ([string]::Empty) -replace '`\d+', ([string]::Empty) } } |