| | | 1 | | using Microsoft.Extensions.DependencyInjection; |
| | | 2 | | |
| | | 3 | | using System.Diagnostics.CodeAnalysis; |
| | | 4 | | using System.Reflection; |
| | | 5 | | |
| | | 6 | | namespace NexusLabs.Needlr.Injection.Reflection.PluginFactories; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Plugin factory that uses runtime reflection to scan assemblies for types that implement specified interfaces, |
| | | 10 | | /// are decorated with particular attributes, or meet both criteria. All discovered types are instantiated |
| | | 11 | | /// using their parameterless constructors. |
| | | 12 | | /// </summary> |
| | | 13 | | /// <remarks> |
| | | 14 | | /// This factory uses runtime reflection and is not compatible with NativeAOT or trimming. |
| | | 15 | | /// For AOT scenarios, use the GeneratedPluginFactory from NexusLabs.Needlr.Injection.SourceGen instead. |
| | | 16 | | /// </remarks> |
| | | 17 | | [RequiresUnreferencedCode("ReflectionPluginFactory uses reflection to discover and instantiate plugins. Use GeneratedPlu |
| | | 18 | | public sealed class ReflectionPluginFactory : IPluginFactory |
| | | 19 | | { |
| | | 20 | | /// <inheritdoc /> |
| | | 21 | | /// <remarks> |
| | | 22 | | /// <para> |
| | | 23 | | /// Only non-abstract, non-generic classes assignable to <typeparamref name="TPlugin"/> are instantiated. |
| | | 24 | | /// Types that cannot be loaded from an assembly are skipped. |
| | | 25 | | /// </para> |
| | | 26 | | /// <para> |
| | | 27 | | /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first), |
| | | 28 | | /// then alphabetically by fully qualified type name for deterministic execution order. |
| | | 29 | | /// </para> |
| | | 30 | | /// </remarks> |
| | | 31 | | public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin>( |
| | | 32 | | IEnumerable<Assembly> assemblies) |
| | | 33 | | where TPlugin : class |
| | | 34 | | { |
| | 734 | 35 | | return GetTypesFromAssemblies(assemblies) |
| | 238781 | 36 | | .Where(t => IsValidPluginType(t) && t.IsAssignableTo(typeof(TPlugin))) |
| | 2223 | 37 | | .Select(t => (Type: t, Order: GetPluginOrder(t))) |
| | 2223 | 38 | | .OrderBy(x => x.Order) |
| | 2223 | 39 | | .ThenBy(x => x.Type.FullName, StringComparer.Ordinal) |
| | 2957 | 40 | | .Select(x => CreateInstance<TPlugin>(x.Type)); |
| | | 41 | | } |
| | | 42 | | |
| | | 43 | | /// <inheritdoc /> |
| | | 44 | | /// <remarks> |
| | | 45 | | /// <para> |
| | | 46 | | /// Only non-abstract, non-generic classes decorated with <typeparamref name="TAttribute"/> are instantiated. |
| | | 47 | | /// Types that cannot be loaded from an assembly are skipped. The attribute is searched in the type hierarchy |
| | | 48 | | /// using inheritance. |
| | | 49 | | /// </para> |
| | | 50 | | /// <para> |
| | | 51 | | /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first), |
| | | 52 | | /// then alphabetically by fully qualified type name for deterministic execution order. |
| | | 53 | | /// </para> |
| | | 54 | | /// </remarks> |
| | | 55 | | public IEnumerable<object> CreatePluginsWithAttributeFromAssemblies<TAttribute>( |
| | | 56 | | IEnumerable<Assembly> assemblies) |
| | | 57 | | where TAttribute : Attribute |
| | | 58 | | { |
| | 7 | 59 | | return GetTypesFromAssemblies(assemblies) |
| | 7 | 60 | | .Where(t => |
| | 1582 | 61 | | IsValidPluginType(t) && |
| | 1582 | 62 | | HasAttribute<TAttribute>(t)) |
| | 12 | 63 | | .Select(t => (Type: t, Order: GetPluginOrder(t))) |
| | 12 | 64 | | .OrderBy(x => x.Order) |
| | 12 | 65 | | .ThenBy(x => x.Type.FullName, StringComparer.Ordinal) |
| | 19 | 66 | | .Select(x => CreateInstance(x.Type)); |
| | | 67 | | } |
| | | 68 | | |
| | | 69 | | /// <inheritdoc /> |
| | | 70 | | /// <remarks> |
| | | 71 | | /// <para> |
| | | 72 | | /// Only non-abstract, non-generic classes assignable to <typeparamref name="TPlugin"/> |
| | | 73 | | /// and decorated with <typeparamref name="TAttribute"/> are instantiated. |
| | | 74 | | /// Types that cannot be loaded from an assembly are skipped. The attribute is |
| | | 75 | | /// searched in the type hierarchy using inheritance. |
| | | 76 | | /// </para> |
| | | 77 | | /// <para> |
| | | 78 | | /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first), |
| | | 79 | | /// then alphabetically by fully qualified type name for deterministic execution order. |
| | | 80 | | /// </para> |
| | | 81 | | /// </remarks> |
| | | 82 | | public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin, TAttribute>( |
| | | 83 | | IEnumerable<Assembly> assemblies) |
| | | 84 | | where TPlugin : class |
| | | 85 | | where TAttribute : Attribute |
| | | 86 | | { |
| | 12 | 87 | | return GetTypesFromAssemblies(assemblies) |
| | 12 | 88 | | .Where(t => |
| | 4444 | 89 | | IsValidPluginType(t) && |
| | 4444 | 90 | | t.IsAssignableTo(typeof(TPlugin)) && |
| | 4444 | 91 | | HasAttribute<TAttribute>(t)) |
| | 23 | 92 | | .Select(t => (Type: t, Order: GetPluginOrder(t))) |
| | 23 | 93 | | .OrderBy(x => x.Order) |
| | 23 | 94 | | .ThenBy(x => x.Type.FullName, StringComparer.Ordinal) |
| | 35 | 95 | | .Select(x => CreateInstance<TPlugin>(x.Type)); |
| | | 96 | | } |
| | | 97 | | |
| | | 98 | | /// <summary> |
| | | 99 | | /// Gets all types from the provided assemblies, handling assembly loading exceptions gracefully. |
| | | 100 | | /// </summary> |
| | | 101 | | /// <param name="assemblies">The assemblies to extract types from.</param> |
| | | 102 | | /// <returns>An enumerable of types that were successfully loaded.</returns> |
| | | 103 | | private static IEnumerable<Type> GetTypesFromAssemblies(IEnumerable<Assembly> assemblies) |
| | | 104 | | { |
| | 753 | 105 | | return assemblies |
| | 753 | 106 | | .SelectMany(assembly => |
| | 753 | 107 | | { |
| | 753 | 108 | | try |
| | 753 | 109 | | { |
| | 912 | 110 | | return assembly.GetTypes(); |
| | 753 | 111 | | } |
| | 753 | 112 | | catch (ReflectionTypeLoadException ex) |
| | 753 | 113 | | { |
| | 1 | 114 | | return ex.Types.OfType<Type>(); |
| | 753 | 115 | | } |
| | 0 | 116 | | catch |
| | 753 | 117 | | { |
| | 0 | 118 | | return []; |
| | 753 | 119 | | } |
| | 1665 | 120 | | }); |
| | | 121 | | } |
| | | 122 | | |
| | | 123 | | /// <summary> |
| | | 124 | | /// Determines if a type is a valid plugin type (non-abstract, non-generic class with parameterless constructor). |
| | | 125 | | /// </summary> |
| | | 126 | | /// <param name="type">The type to validate.</param> |
| | | 127 | | /// <returns>True if the type is a valid plugin type; otherwise, false.</returns> |
| | | 128 | | private static bool IsValidPluginType(Type type) |
| | | 129 | | { |
| | 244807 | 130 | | return type.IsClass && |
| | 244807 | 131 | | !type.IsAbstract && |
| | 244807 | 132 | | !type.IsGenericTypeDefinition && |
| | 244807 | 133 | | HasParameterlessConstructor(type) && |
| | 244807 | 134 | | !HasDoNotAutoRegisterAttribute(type); |
| | | 135 | | } |
| | | 136 | | |
| | | 137 | | /// <summary> |
| | | 138 | | /// Determines if a type has the [DoNotAutoRegister] attribute directly applied. |
| | | 139 | | /// </summary> |
| | | 140 | | /// <param name="type">The type to check.</param> |
| | | 141 | | /// <returns>True if the type has the attribute; otherwise, false.</returns> |
| | | 142 | | private static bool HasDoNotAutoRegisterAttribute(Type type) |
| | | 143 | | { |
| | 140081 | 144 | | return type.GetCustomAttribute<DoNotAutoRegisterAttribute>(inherit: false) is not null; |
| | | 145 | | } |
| | | 146 | | |
| | | 147 | | /// <summary> |
| | | 148 | | /// Determines if a type has a parameterless constructor. |
| | | 149 | | /// </summary> |
| | | 150 | | /// <param name="type">The type to check.</param> |
| | | 151 | | /// <returns>True if the type has a parameterless constructor; otherwise, false.</returns> |
| | | 152 | | private static bool HasParameterlessConstructor(Type type) |
| | | 153 | | { |
| | 170820 | 154 | | return type.GetConstructor(Type.EmptyTypes) is not null; |
| | | 155 | | } |
| | | 156 | | |
| | | 157 | | /// <summary> |
| | | 158 | | /// Determines if a type has the specified attribute in its type hierarchy. |
| | | 159 | | /// </summary> |
| | | 160 | | /// <typeparam name="TAttribute">The attribute type to search for.</typeparam> |
| | | 161 | | /// <param name="type">The type to check for the attribute.</param> |
| | | 162 | | /// <returns>True if the type has the attribute; otherwise, false.</returns> |
| | | 163 | | private static bool HasAttribute<TAttribute>(Type type) where TAttribute : Attribute |
| | | 164 | | { |
| | 1048 | 165 | | return type.GetCustomAttribute<TAttribute>(inherit: true) is not null; |
| | | 166 | | } |
| | | 167 | | |
| | | 168 | | /// <summary> |
| | | 169 | | /// Gets the plugin execution order from the <see cref="PluginOrderAttribute"/>. |
| | | 170 | | /// </summary> |
| | | 171 | | /// <param name="type">The type to check.</param> |
| | | 172 | | /// <returns>The order value, or 0 if no attribute is present.</returns> |
| | | 173 | | private static int GetPluginOrder(Type type) |
| | | 174 | | { |
| | 2258 | 175 | | var attr = type.GetCustomAttribute<PluginOrderAttribute>(inherit: false); |
| | 2258 | 176 | | return attr?.Order ?? 0; |
| | | 177 | | } |
| | | 178 | | |
| | | 179 | | /// <summary> |
| | | 180 | | /// Creates an instance of the specified type. |
| | | 181 | | /// </summary> |
| | | 182 | | /// <param name="type">The type to instantiate.</param> |
| | | 183 | | /// <returns>An instance of the type.</returns> |
| | | 184 | | private static object CreateInstance(Type type) |
| | | 185 | | { |
| | 12 | 186 | | return Activator.CreateInstance(type)!; |
| | | 187 | | } |
| | | 188 | | |
| | | 189 | | /// <summary> |
| | | 190 | | /// Creates an instance of the specified type and casts it to the target type. |
| | | 191 | | /// </summary> |
| | | 192 | | /// <typeparam name="T">The target type to cast to.</typeparam> |
| | | 193 | | /// <param name="type">The type to instantiate.</param> |
| | | 194 | | /// <returns>An instance of the type cast to the target type.</returns> |
| | | 195 | | private static T CreateInstance<T>(Type type) where T : class |
| | | 196 | | { |
| | 2246 | 197 | | return (T)Activator.CreateInstance(type)!; |
| | | 198 | | } |
| | | 199 | | } |