AssemblyTool/RecodeItLib/AutoMapper/ReCodeItAutoMapper.cs

546 lines
20 KiB
C#
Raw Normal View History

2024-06-15 16:21:12 -04:00
using Mono.Cecil;
2024-06-16 01:48:48 -04:00
using Mono.Collections.Generic;
2024-06-15 16:45:34 -04:00
using ReCodeIt.Models;
2024-06-16 01:48:48 -04:00
using ReCodeIt.ReMapper;
2024-06-15 16:21:12 -04:00
using ReCodeIt.Utils;
using ReCodeItLib.Utils;
namespace ReCodeItLib.AutoMapper;
public class ReCodeItAutoMapper
{
private List<MappingPair> MappingPairs { get; set; } = [];
2024-06-15 19:13:52 -04:00
private List<string> CompilerGeneratedClasses = [];
2024-06-15 16:21:12 -04:00
private List<TypeDefinition> AllTypes { get; set; } = [];
private List<string> AlreadyChangedNames { get; set; } = [];
2024-06-15 21:05:06 -04:00
private static AutoMapperSettings Settings => DataProvider.Settings.AutoMapper;
2024-06-15 16:21:12 -04:00
2024-06-16 00:19:02 -04:00
private static bool Error { get; set; } = false;
private int FailureCount { get; set; } = 0;
2024-06-16 01:48:48 -04:00
private int TotalFieldRenameCount { get; set; } = 0;
private int TotalPropertyRenameCount { get; set; } = 0;
2024-06-15 22:12:34 -04:00
/// <summary>
/// Start the automapping process
/// </summary>
2024-06-15 16:21:12 -04:00
public void InitializeAutoMapping()
{
Logger.ClearLog();
Logger.Log($"Starting Auto Mapping...");
// Clear any previous pairs
MappingPairs = [];
2024-06-15 19:13:52 -04:00
CompilerGeneratedClasses = [];
AllTypes = [];
AlreadyChangedNames = [];
2024-06-15 19:13:52 -04:00
2024-06-17 17:29:26 -04:00
DataProvider.LoadAssemblyDefinition(Settings.AssemblyPath);
2024-06-16 13:56:55 -04:00
2024-06-16 00:19:02 -04:00
Error = false;
FailureCount = 0;
2024-06-16 01:48:48 -04:00
TotalFieldRenameCount = 0;
TotalPropertyRenameCount = 0;
2024-06-16 00:19:02 -04:00
2024-06-15 19:13:52 -04:00
FindCompilerGeneratedObjects(DataProvider.ModuleDefinition.Types);
2024-06-15 16:21:12 -04:00
2024-06-15 19:13:52 -04:00
var types = DataProvider.ModuleDefinition.Types;
GetAllTypes(types);
Logger.Log($"Found {CompilerGeneratedClasses.Count - AllTypes.Count} potential remappable types");
Logger.Log($"Found {CompilerGeneratedClasses.Count} compiler generated objects");
2024-06-15 19:13:52 -04:00
foreach (var type in types)
2024-06-15 16:21:12 -04:00
{
2024-06-16 16:21:42 -04:00
// We dont want to do anything with compiler generated objects
if (CompilerGeneratedClasses.Contains(type.Name))
{
continue;
}
2024-06-15 16:21:12 -04:00
MappingPairs.AddRange(FilterFieldNames(type));
MappingPairs.AddRange(FilterPropertyNames(type));
2024-06-16 16:21:42 -04:00
if (Settings.SearchMethods)
{
MappingPairs.AddRange(GatherFromMethods(type));
}
}
PrimaryTypeNameFilter();
2024-06-15 21:05:06 -04:00
SanitizeProposedNames();
2024-06-16 00:19:02 -04:00
StartRenameProcess();
2024-06-15 21:05:06 -04:00
WriteChanges();
2024-06-15 16:21:12 -04:00
}
private void GetAllTypes(Collection<TypeDefinition> types)
{
AllTypes.AddRange(types);
foreach (var type in types)
{
if (type.HasNestedTypes)
{
GetAllTypes(type.NestedTypes);
}
}
}
2024-06-15 22:12:34 -04:00
/// <summary>
/// Finds any compiler generated code so we can ignore it, its mostly LINQ garbage
/// </summary>
/// <param name="types"></param>
2024-06-16 01:48:48 -04:00
private void FindCompilerGeneratedObjects(Collection<TypeDefinition> types)
2024-06-15 19:13:52 -04:00
{
foreach (var typeDefinition in types)
{
if (typeDefinition.IsClass || typeDefinition.IsInterface || typeDefinition.IsValueType) // Check for class or struct
{
if (typeDefinition.HasCustomAttributes &&
typeDefinition.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute"))
{
string typeName = typeDefinition.Name;
CompilerGeneratedClasses.Add(typeName);
//Logger.Log($"Compiler Generated object found: {typeName}", ConsoleColor.Yellow);
}
}
if (typeDefinition.NestedTypes.Count > 0)
{
FindCompilerGeneratedObjects(typeDefinition.NestedTypes);
}
}
}
2024-06-16 16:21:42 -04:00
#region METHODS
private List<MappingPair> GatherFromMethods(TypeDefinition type)
{
var methodsWithTypes = new List<MappingPair>();
// Handle nested types recursively
foreach (var nestedType in type.NestedTypes)
{
methodsWithTypes.AddRange(GatherFromMethods(nestedType));
}
var methods = type.Methods
// We only want methods with parameters
.Where(m => m.HasParameters)
// Only want parameter names of a certain length
.Where(m => m.Parameters.Any(p => p.Name.Length > Settings.MinLengthToMatch));
// Now go over over all filterd methods manually, because fuck this with linq
foreach (var method in methods)
{
var parmNames = method.Parameters.Select(p => p.Name);
var parmTypes = method.Parameters.Select(p => p.ParameterType.Name);
// Now over all parameters in the method
foreach (var parm in method.Parameters)
{
// We dont want blacklisted items
if (Settings.MethodParamaterBlackList.Contains(parm.ParameterType.Name.TrimAfterSpecialChar())
|| Settings.TypesToIgnore.Contains(parm.ParameterType.Name.TrimAfterSpecialChar()))
{
continue;
}
if (parm.ParameterType.Resolve() == null) { continue; }
//Logger.Log($"Method Data Found");
//Logger.Log($"Parameter count: {method.Parameters.Count}");
//Logger.Log($"Paremeter Names: {string.Join(", ", parmNames)}");
//Logger.Log($"Paremeter Types: {string.Join(", ", parmTypes)}\n");
var mapPair = new MappingPair(
parm.ParameterType.Resolve(),
parm.Name,
parm.ParameterType.Resolve().IsInterface,
parm.ParameterType.Name.Contains("Struct"),
true);
mapPair.AutoMappingResult = AutoMappingResult.Match_From_Method;
2024-06-16 16:21:42 -04:00
methodsWithTypes.Add(mapPair);
}
}
return methodsWithTypes;
}
#endregion METHODS
#region FIELDS_PROPERTIES
2024-06-15 16:21:12 -04:00
/// <summary>
/// Pair field declaring types with their names
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private List<MappingPair> FilterFieldNames(TypeDefinition type)
{
var fieldsWithTypes = new List<MappingPair>();
// Handle nested types recursively
foreach (var nestedType in type.NestedTypes)
{
fieldsWithTypes.AddRange(FilterFieldNames(nestedType));
}
var fields = type.Fields
// we dont want names shorter than 4
.Where(f => f.FieldType.Name.Length > 3)
// Skip value types
.Where(f => !f.FieldType.IsValueType)
2024-06-16 00:19:02 -04:00
// TODO: Renaming arrays is strange, come back to this later
.Where(p => !p.FieldType.IsArray)
2024-06-15 16:21:12 -04:00
// We dont want fields in the system type ignore list
2024-06-15 16:45:34 -04:00
.Where(f => !Settings.TypesToIgnore.Contains(f.FieldType.Name.TrimAfterSpecialChar()));
2024-06-15 16:21:12 -04:00
// Include fields from the current type
foreach (var field in fields)
{
2024-06-16 00:19:02 -04:00
//Logger.Log($"Collecting Field: OriginalTypeRef: {field.FieldType.Name.TrimAfterSpecialChar()} Field Name: {field.Name}");
2024-06-15 16:21:12 -04:00
2024-06-16 01:48:48 -04:00
var typeDef = field.FieldType.Resolve();
// Dont rename things we cant resolve
if (typeDef is null) { continue; }
2024-06-16 16:21:42 -04:00
var pair = new MappingPair(
2024-06-16 01:48:48 -04:00
typeDef,
2024-06-15 21:05:06 -04:00
field.Name,
field.FieldType.Name.Contains("Interface"),
2024-06-15 22:12:34 -04:00
field.FieldType.Name.Contains("Struct"),
2024-06-16 16:21:42 -04:00
field.IsPublic);
pair.AutoMappingResult = AutoMappingResult.Match_From_Field;
2024-06-16 16:21:42 -04:00
fieldsWithTypes.Add(pair);
2024-06-15 16:21:12 -04:00
}
return fieldsWithTypes;
}
/// <summary>
/// Pair field declaring types with their names
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private IEnumerable<MappingPair> FilterPropertyNames(TypeDefinition type)
2024-06-15 16:21:12 -04:00
{
var propertiesWithTypes = new List<MappingPair>();
// Handle nested types recursively
foreach (var nestedType in type.NestedTypes)
{
propertiesWithTypes.AddRange(FilterPropertyNames(nestedType));
}
var properties = type.Properties
// we dont want names shorter than 4
.Where(p => p.PropertyType.Name.Length > 3)
// Skip value types
.Where(p => !p.PropertyType.IsValueType)
2024-06-16 00:19:02 -04:00
// TODO: Renaming arrays is strange, come back to this later
.Where(p => !p.PropertyType.IsArray)
2024-06-15 16:21:12 -04:00
// We dont want fields in the global ignore list
2024-06-15 16:45:34 -04:00
.Where(p => !Settings.TypesToIgnore.Contains(p.PropertyType.Name.TrimAfterSpecialChar()));
2024-06-15 16:21:12 -04:00
// Include fields from the current type
foreach (var property in properties)
{
2024-06-16 00:19:02 -04:00
//Logger.Log($"Collecting Property: OriginalTypeRef: {property.PropertyType.Name.TrimAfterSpecialChar()} Field Name: {property.Name}");
2024-06-15 16:21:12 -04:00
2024-06-16 01:48:48 -04:00
var typeDef = property.PropertyType.Resolve();
// Dont rename things we cant resolve
if (typeDef is null) { continue; }
2024-06-15 21:05:06 -04:00
2024-06-16 16:21:42 -04:00
var mapPair = new MappingPair(
2024-06-16 01:48:48 -04:00
typeDef,
2024-06-15 21:05:06 -04:00
property.Name,
property.PropertyType.Name.Contains("Interface"),
2024-06-16 01:48:48 -04:00
property.PropertyType.Name.Contains("Struct"),
2024-06-16 16:21:42 -04:00
true);
mapPair.AutoMappingResult = AutoMappingResult.Match_From_Property;
2024-06-16 16:21:42 -04:00
propertiesWithTypes.Add(mapPair);
2024-06-15 16:21:12 -04:00
}
return propertiesWithTypes;
}
2024-06-16 16:21:42 -04:00
#endregion FIELDS_PROPERTIES
#region FILTER
2024-06-15 16:45:34 -04:00
/// <summary>
2024-06-15 21:05:06 -04:00
/// This giant linq statement handles all of the filtering once the initial gathering of fields
/// and properties is complete
2024-06-15 16:45:34 -04:00
/// </summary>
private void PrimaryTypeNameFilter()
2024-06-15 16:21:12 -04:00
{
// Filter types to the ones we're looking for
var mappingPairs = MappingPairs
2024-06-15 16:45:34 -04:00
// Filter based on length, short lengths dont make good class names
.Where(pair => pair.Name.Length >= Settings.MinLengthToMatch)
2024-06-15 16:21:12 -04:00
2024-06-15 21:05:06 -04:00
// Filter out anything that doesnt start with our specified tokens (Where
2024-06-16 00:19:02 -04:00
// pair.OriginalTypeRef.Name is the property OriginalTypeRef name `Class1202` and token
// is start identifer we are looking for `GClass`
2024-06-15 17:36:40 -04:00
.Where(pair => Settings.TokensToMatch
2024-06-16 01:48:48 -04:00
.Any(token => pair.OriginalTypeDefinition.Name.StartsWith(token)))
2024-06-15 17:36:40 -04:00
// Filter out anything that has the same name as the type, we cant remap those
.Where(pair => !Settings.TokensToMatch
.Any(token => pair.Name.ToLower().StartsWith(token.ToLower())))
// Filter based on direct name blacklist (Where pair.Name is the property name and token
// is blacklisted item `Columns`
.Where(pair => !Settings.PropertyFieldBlackList
.Any(token => pair.Name.ToLower().StartsWith(token.ToLower())))
2024-06-15 21:05:06 -04:00
// Filter out backing fields
/// This is slow, but oh well
.Where(pair => !pair.Name.ToCharArray().Contains('<')).ToList();
MappingPairs = mappingPairs;
SecondaryTypeNameFilter();
}
/// <summary>
/// This is where we filter down based on more specific parameters
/// </summary>
/// <param name="mappingPairs"></param>
private void SecondaryTypeNameFilter()
{
// Filter property/field names by required number of matches
MappingPairs = MappingPairs
.GroupBy(pair => pair.OriginalPropOrFieldName.TrimAfterSpecialChar())
.Where(group => group.Count() > Settings.RequiredMatches)
.SelectMany(group => group)
.ToList()
// We dont want names that already exist to be considered
.Where(pair => AllTypes
.Any(token => !pair.OriginalTypeDefinition.FullName.Contains(token.FullName))).ToList();
2024-06-15 21:05:06 -04:00
FinalGroupAndSelect();
}
2024-06-16 16:21:42 -04:00
/// <summary>
/// This is where we make sure everything is original
/// </summary>
private void FinalGroupAndSelect()
{
MappingPairs = MappingPairs
2024-06-15 17:36:40 -04:00
// We only want types once, so make it unique
2024-06-16 01:48:48 -04:00
.GroupBy(pair => pair.OriginalTypeDefinition.FullName)
2024-06-15 17:36:40 -04:00
.Select(group => group.First())
2024-06-15 21:05:06 -04:00
.GroupBy(pair => pair.Name)
.Select(group => group.First()).ToList();
2024-06-15 21:05:06 -04:00
}
2024-06-15 16:21:12 -04:00
2024-06-16 16:21:42 -04:00
#endregion FILTER
#region OUTPUT
2024-06-15 22:12:34 -04:00
/// <summary>
/// Sanitizes and prepares mapping pairs for remapping once filtering is complete.
/// </summary>
2024-06-15 21:05:06 -04:00
private void SanitizeProposedNames()
{
foreach (var pair in MappingPairs)
2024-06-15 16:21:12 -04:00
{
2024-06-15 21:05:06 -04:00
char first = pair.Name.ToCharArray().ElementAt(0);
if (first.Equals('_'))
{
pair.Name = string.Concat("", pair.Name.AsSpan(1));
}
// Re-run incase prefix removed
first = pair.Name.ToCharArray().ElementAt(0);
if (char.IsLower(first))
{
pair.Name = string.Concat(char.ToUpper(first).ToString(), pair.Name.AsSpan(1));
}
if (pair.IsInterface)
{
pair.Name = string.Concat("I", pair.Name.AsSpan(0));
2024-06-16 19:20:42 -04:00
pair.Name = pair.Name.Replace("Class", "");
2024-06-15 21:05:06 -04:00
}
2024-06-16 01:48:48 -04:00
/*
2024-06-15 22:12:34 -04:00
// Try and remove any trailing 's' that exist
if (pair.WasCollection)
{
if (pair.Name.ToLower().EndsWith('s'))
{
pair.Name = pair.Name.Substring(0, pair.Name.Length - 1);
}
}
2024-06-16 01:48:48 -04:00
*/
2024-06-15 21:05:06 -04:00
2024-06-16 19:20:42 -04:00
if (pair.IsInterface)
{
// Replace class if it exists
pair.Name = pair.Name.Replace("Class", "");
}
else if (pair.IsStruct)
{
pair.Name = string.Concat(pair.Name, "Struct");
}
else
{
pair.Name = string.Concat(pair.Name, "Class");
2024-06-15 21:05:06 -04:00
}
Logger.Log($"------------------------------------------------------------------------");
2024-06-16 01:48:48 -04:00
Logger.Log($"Original Name: {pair.OriginalTypeDefinition.FullName} : Sanitized Name: {pair.Name}");
2024-06-15 21:05:06 -04:00
Logger.Log($"Matched From Name: {pair.OriginalPropOrFieldName}");
Logger.Log($"IsInterface: {pair.IsInterface}");
Logger.Log($"IsStruct: {pair.IsStruct}");
Logger.Log($"Is match from: {pair.AutoMappingResult}");
2024-06-15 21:05:06 -04:00
Logger.Log($"------------------------------------------------------------------------");
2024-06-15 16:21:12 -04:00
}
2024-06-15 21:37:55 -04:00
Logger.Log($"Automatically remapped {MappingPairs.Count()} objects");
2024-06-15 21:05:06 -04:00
}
2024-06-15 16:21:12 -04:00
2024-06-16 00:19:02 -04:00
/// <summary>
/// Start renaming assembly definitions
/// </summary>
private void StartRenameProcess()
{
2024-06-16 01:48:48 -04:00
// Gather up any matches we have
2024-06-16 00:19:02 -04:00
foreach (var type in DataProvider.ModuleDefinition.Types.ToArray())
{
foreach (var pair in MappingPairs.ToArray())
{
GatherMatchedTypeRefs(pair, type);
}
}
2024-06-16 01:48:48 -04:00
// Rename Types to matched types
2024-06-16 00:19:02 -04:00
foreach (var pair in MappingPairs)
{
if (pair.NewTypeRef != null && !AlreadyChangedNames.Contains(pair.Name))
2024-06-16 00:19:02 -04:00
{
Logger.Log($"------------------------------------------------------------------------", ConsoleColor.Green);
2024-06-16 01:48:48 -04:00
Logger.Log($"Renaming: {pair.OriginalTypeDefinition.Name} to {pair.Name}", ConsoleColor.Green);
Logger.Log($"Is match from method: {pair.AutoMappingResult}", ConsoleColor.Green);
2024-06-16 01:48:48 -04:00
var fieldCount = RenameHelper.RenameAllFields(
pair.OriginalTypeDefinition.Name,
pair.Name,
DataProvider.ModuleDefinition.Types);
var propCount = RenameHelper.RenameAllProperties(
pair.OriginalTypeDefinition.Name,
pair.Name,
DataProvider.ModuleDefinition.Types);
TotalFieldRenameCount += fieldCount;
TotalPropertyRenameCount += propCount;
Logger.Log($"Renamed: {fieldCount} fields", ConsoleColor.Green);
Logger.Log($"Renamed: {propCount} properties", ConsoleColor.Green);
2024-06-16 00:19:02 -04:00
Logger.Log($"------------------------------------------------------------------------", ConsoleColor.Green);
2024-06-16 01:48:48 -04:00
AlreadyChangedNames.Add(pair.Name);
2024-06-16 01:48:48 -04:00
pair.NewTypeRef.Name = pair.Name;
pair.HasBeenRenamed = true;
continue;
}
if (pair.HasBeenRenamed) { continue; }
// Set some error codes
if (AlreadyChangedNames.Contains(pair.Name))
{
pair.AutoMappingResult = AutoMappingResult.Fail_From_Already_Contained_Name;
}
if (pair.NewTypeRef == null)
{
pair.AutoMappingResult = AutoMappingResult.Fail_From_New_Type_Ref_Null;
2024-06-16 00:19:02 -04:00
}
}
// Do a final error check
foreach (var pair in MappingPairs)
{
if (!pair.HasBeenRenamed)
{
Logger.Log($"------------------------------------------------------------------------", ConsoleColor.Red);
2024-06-16 01:48:48 -04:00
Logger.Log($"Renaming: {pair.OriginalTypeDefinition.Name} to {pair.Name} has failed", ConsoleColor.Red);
Logger.Log($"Result Code: {pair.AutoMappingResult}", ConsoleColor.Red);
2024-06-16 00:19:02 -04:00
Logger.Log($"IsInterface: {pair.IsInterface}", ConsoleColor.Red);
Logger.Log($"IsStruct: {pair.IsStruct}", ConsoleColor.Red);
Logger.Log($"------------------------------------------------------------------------", ConsoleColor.Red);
FailureCount++;
Error = true;
}
}
}
/// <summary>
/// Recursively handle all renaming on nested types on a given type
/// </summary>
/// <param name="pair"></param>
/// <param name="type"></param>
private void GatherMatchedTypeRefs(MappingPair pair, TypeDefinition type)
{
// Handle nested types recursively
foreach (var nestedType in type.NestedTypes.ToArray())
{
GatherMatchedTypeRefs(pair, nestedType);
}
2024-06-16 01:48:48 -04:00
if (type == pair.OriginalTypeDefinition)
2024-06-16 00:19:02 -04:00
{
pair.NewTypeRef = type;
}
}
2024-06-15 21:05:06 -04:00
private void WriteChanges()
{
2024-06-17 17:29:26 -04:00
var path = DataProvider.WriteAssemblyDefinition(Settings.OutputPath);
2024-06-16 03:43:00 -04:00
var fieldCountMatchResult = MappingPairs.Where(x => x.AutoMappingResult == AutoMappingResult.Match_From_Property).Count();
var propertyCountMatchResult = MappingPairs.Where(x => x.AutoMappingResult == AutoMappingResult.Match_From_Property).Count();
var methodCountMatchResult = MappingPairs.Where(x => x.AutoMappingResult == AutoMappingResult.Match_From_Method).Count();
2024-06-16 16:21:42 -04:00
2024-06-16 03:43:00 -04:00
Logger.Log($"-------------------------------RESULT-----------------------------------", ConsoleColor.Green);
Logger.Log($"Complete: Assembly written to `{path}`", ConsoleColor.Green);
Logger.Log($"Found {MappingPairs.Count()} automatic remaps", ConsoleColor.Green);
2024-06-16 16:21:42 -04:00
Logger.Log($"Found {fieldCountMatchResult} automatic remaps from fields", ConsoleColor.Green);
Logger.Log($"Found {propertyCountMatchResult} automatic remaps from properties", ConsoleColor.Green);
Logger.Log($"Found {methodCountMatchResult} automatic remaps from methods", ConsoleColor.Green);
2024-06-16 03:43:00 -04:00
Logger.Log($"Renamed {TotalFieldRenameCount} fields", ConsoleColor.Green);
Logger.Log($"Renamed {TotalPropertyRenameCount} properties", ConsoleColor.Green);
2024-06-16 16:21:42 -04:00
Logger.Log($"Failed to rename: {FailureCount} mapping pairs", (FailureCount == 0 ? ConsoleColor.Green : ConsoleColor.Red));
2024-06-16 03:43:00 -04:00
Logger.Log($"------------------------------------------------------------------------", ConsoleColor.Green);
2024-06-15 16:21:12 -04:00
}
2024-06-16 16:21:42 -04:00
#endregion OUTPUT
2024-06-15 16:21:12 -04:00
}