NanoXLSX.Core 3.0.0-rc.5
Loading...
Searching...
No Matches
PluginLoader.cs
1/*
2 * NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3 * Copyright Raphael Stoeckli © 2026
4 * This library is licensed under the MIT License.
5 * You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6 */
7
8using System;
9using System.Collections.Generic;
10using System.Diagnostics.CodeAnalysis;
11using System.IO;
12using System.Linq;
13using System.Reflection;
16
17namespace NanoXLSX.Registry
18{
22 public static class PlugInLoader
23 {
24 private static bool initialized;
25 private static readonly object _lock = new object();
26
27 private static readonly Dictionary<string, PlugInInstance> plugInClasses = new Dictionary<string, PlugInInstance>();
28 private static readonly Dictionary<string, List<PlugInInstance>> queuePlugInClasses = new Dictionary<string, List<PlugInInstance>>();
29
34 public static bool Initialize()
35 {
36
37 if (initialized)
38 {
39 return false;
40 }
41 lock (_lock)
42 {
43 LoadReferencedAssemblies();
44 initialized = true;
45 return initialized;
46 }
47 }
48
52 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
53 private static void LoadReferencedAssemblies()
54 {
55 Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
56
57 // 1. Register plugins from already-loaded assemblies
58 foreach (Assembly assembly in loadedAssemblies)
59 {
60 if (!assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location))
61 {
62 try
63 {
64 RegisterPlugIns(assembly);
65 }
66 catch (Exception ex)
67 {
68 System.Diagnostics.Debug.WriteLine($"Failed to register plugins from {assembly.Location}: {ex.Message}");
69 }
70 }
71 }
72
73 // 2. Load any remaining DLLs that haven't been loaded yet
74 IEnumerable<string> allLoadedPaths = loadedAssemblies
75 .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))
76 .Select(a => a.Location);
77 HashSet<string> loadedPaths = new HashSet<string>(allLoadedPaths, StringComparer.InvariantCultureIgnoreCase);
78
79 List<string> referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
80 .Where(path => !loadedPaths.Contains(path))
81 .ToList();
82
83 foreach (string path in referencedPaths)
84 {
85 try
86 {
87 Assembly assembly = Assembly.LoadFrom(path);
88 RegisterPlugIns(assembly);
89 }
90 catch (Exception ex)
91 {
92 System.Diagnostics.Debug.WriteLine($"Failed to load assembly: {path} - {ex.Message}");
93 }
94 }
95 }
96
102 internal static void InjectPlugins(List<Type> pluginTypes)
103 {
104 lock (_lock)
105 {
106 // Collect all types decorated with the NanoXlsxPlugInAttribute.
107 IEnumerable<Type> replacingPluginTypes = pluginTypes
108 .Where(t => t.GetCustomAttribute<NanoXlsxPlugInAttribute>() != null);
109
110 // Collect all types decorated with the NanoXlsxQueuePlugInAttribute.
111 IEnumerable<Type> queuePluginTypes = pluginTypes
112 .Where(t => t.GetCustomAttributes<NanoXlsxQueuePlugInAttribute>().Any());
113
114 // Pass the collected types to the appropriate handlers.
115 HandleReplacingPlugIns(replacingPluginTypes);
116 HandleQueuePlugIns(queuePluginTypes);
117 initialized = true;
118 }
119 }
120
125 internal static void DisposePlugins()
126 {
127 lock (_lock)
128 {
129 plugInClasses.Clear();
130 queuePlugInClasses.Clear();
131 initialized = false;
132 }
133 }
134
139 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
140 private static void RegisterPlugIns(Assembly assembly)
141 {
142 IEnumerable<Type> replacingPlugInTypes = GetAssemblyPlugInsByType(assembly, typeof(NanoXlsxPlugInAttribute));
143 IEnumerable<Type> queuePlugInTypes = GetAssemblyPlugInsByType(assembly, typeof(NanoXlsxQueuePlugInAttribute));
144 HandleReplacingPlugIns(replacingPlugInTypes);
145 HandleQueuePlugIns(queuePlugInTypes);
146 }
147
148
155 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
156 private static List<Type> GetAssemblyPlugInsByType(Assembly assembly, Type attributeType)
157 {
158 List<Type> plugInTypes = new List<Type>();
159 Type plugInInterface = typeof(IPlugin);
160 Type[] allTypes = assembly.GetTypes();
161
162 for (int i = 0; i < allTypes.Length; i++)
163 {
164 Type type = allTypes[i];
165 if (type.IsClass && !type.IsAbstract &&
166 plugInInterface.IsAssignableFrom(type) &&
167 type.GetCustomAttribute(attributeType) != null)
168 {
169 plugInTypes.Add(type);
170 }
171 }
172 return plugInTypes;
173 }
174
179 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
180 private static void HandleReplacingPlugIns(IEnumerable<Type> plugInTypes)
181 {
182 foreach (Type plugInType in plugInTypes)
183 {
184 IEnumerable<NanoXlsxPlugInAttribute> attributes = plugInType.GetCustomAttributes<NanoXlsxPlugInAttribute>();
185 foreach (NanoXlsxPlugInAttribute attribute in attributes)
186 {
187 if (plugInClasses.ContainsKey(attribute.PlugInUUID))
188 {
189 if (attribute.PlugInOrder <= plugInClasses[attribute.PlugInUUID].Order)
190 {
191 // Skip duplicates with lower order numbers
192 plugInClasses[attribute.PlugInUUID] = new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType);
193 }
194 }
195 else if (!plugInClasses.ContainsKey(attribute.PlugInUUID))
196 {
197 plugInClasses.Add(attribute.PlugInUUID, new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType));
198 }
199 }
200 }
201 }
202
207 private static void HandleQueuePlugIns(IEnumerable<Type> queuePlugInTypes)
208 {
209 foreach (Type plugInType in queuePlugInTypes)
210 {
211 IEnumerable<NanoXlsxQueuePlugInAttribute> attributes = plugInType.GetCustomAttributes<NanoXlsxQueuePlugInAttribute>();
212 foreach (var attribute in attributes)
213 {
214 if (!queuePlugInClasses.TryGetValue(attribute.QueueUUID, out var value))
215 {
216 value = new List<PlugInInstance>();
217 queuePlugInClasses.Add(attribute.QueueUUID, value);
218 }
219
220 value.Add(new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType));
221 }
222 }
223 // Sort each list based on PlugInOrder (ascending)
224 foreach (KeyValuePair<string, List<PlugInInstance>> entry in queuePlugInClasses)
225 {
226 entry.Value.Sort((a, b) => a.Order.CompareTo(b.Order));
227 }
228 }
229
239 internal static T GetPlugIn<T>(string plugInUUID, T fallBackInstance)
240 {
241 if (plugInClasses.TryGetValue(plugInUUID, out var plugIn))
242 {
243 return (T)Activator.CreateInstance(plugIn.Type, true);
244 }
245 else
246 {
247 return fallBackInstance;
248 }
249 }
250
261 internal static T GetNextQueuePlugIn<T>(string queueUUID, string lastPlugInUUID, out string currentPlugInUUID)
262 {
263 if (queuePlugInClasses.TryGetValue(queueUUID, out var plugInList) && plugInList.Count > 0)
264 {
265 PlugInInstance plugIn = null;
266 if (lastPlugInUUID == null)
267 {
268 plugIn = plugInList[0];
269 }
270 else
271 {
272 // Find the next plug-in after the currentPlugInUUID
273 int index = plugInList.FindIndex(p => p.UUID == lastPlugInUUID);
274 if (index >= 0 && index + 1 < plugInList.Count)
275 {
276 plugIn = plugInList[index + 1]; // Get the next plug-in in the list
277 }
278 }
279 if (plugIn == null)
280 {
281 currentPlugInUUID = null;
282 return default;
283 }
284 currentPlugInUUID = plugIn.UUID;
285 return (T)Activator.CreateInstance(plugIn.Type, true);
286 }
287 else
288 {
289 currentPlugInUUID = null;
290 return default;
291 }
292 }
293
297 private sealed class PlugInInstance
298 {
302 public string UUID { get; private set; }
306 public int Order { get; private set; }
311 public Type Type { get; private set; }
312
319 internal PlugInInstance(string uuid, int order, Type type)
320 {
321 this.UUID = uuid;
322 this.Order = order;
323 this.Type = type;
324 }
325 }
326 }
327}
int PlugInOrder
Order how the annotated plug-ins are registered in case of duplicate UIDs. The higher number will ove...
Class to register plug-in classes that extends the functionality of NanoXLSX (Core or any other packa...
static bool Initialize()
Initializes the plug-in loader process. If already initialized, the method returns without action.