NanoXLSX.Core 3.0.0-rc.3
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 © 2025
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 var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
56 var loadedPaths = loadedAssemblies
57 .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)) // Exclude dynamic assemblies
58 .Select(a => a.Location)
59 .ToArray();
60 var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
61 .Where(path => !loadedPaths.Contains(path, StringComparer.InvariantCultureIgnoreCase));
62
63 foreach (var path in referencedPaths)
64 {
65 try
66 {
67 Assembly assembly = Assembly.Load(path);
68 RegisterPlugIns(assembly); // Here, several classes are determined that has Attributes, describing them as plug-ins
69 }
70 catch
71 {
72 // Log or handle exceptions if necessary
73 }
74 }
75 }
76
82 internal static void InjectPlugins(List<Type> pluginTypes)
83 {
84 lock (_lock)
85 {
86 // Collect all types decorated with the NanoXlsxPlugInAttribute.
87 IEnumerable<Type> replacingPluginTypes = pluginTypes
88 .Where(t => t.GetCustomAttribute<NanoXlsxPlugInAttribute>() != null);
89
90 // Collect all types decorated with the NanoXlsxQueuePlugInAttribute.
91 IEnumerable<Type> queuePluginTypes = pluginTypes
92 .Where(t => t.GetCustomAttributes<NanoXlsxQueuePlugInAttribute>().Any());
93
94 // Pass the collected types to the appropriate handlers.
95 HandleReplacingPlugIns(replacingPluginTypes);
96 HandleQueuePlugIns(queuePluginTypes);
97 initialized = true;
98 }
99 }
100
105 internal static void DisposePlugins()
106 {
107 lock (_lock)
108 {
109 plugInClasses.Clear();
110 queuePlugInClasses.Clear();
111 initialized = false;
112 }
113 }
114
119 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
120 private static void RegisterPlugIns(Assembly assembly)
121 {
122 IEnumerable<Type> replacingPlugInTypes = GetAssemblyPlugInsByType(assembly, typeof(NanoXlsxPlugInAttribute));
123 IEnumerable<Type> queuePlugInTypes = GetAssemblyPlugInsByType(assembly, typeof(NanoXlsxQueuePlugInAttribute));
124 HandleReplacingPlugIns(replacingPlugInTypes);
125 HandleQueuePlugIns(queuePlugInTypes);
126 }
127
128
135 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
136 private static List<Type> GetAssemblyPlugInsByType(Assembly assembly, Type attributeType)
137 {
138 List<Type> plugInTypes = new List<Type>();
139 Type plugInInterface = typeof(IPlugIn);
140 Type[] allTypes = assembly.GetTypes();
141
142 for (int i = 0; i < allTypes.Length; i++)
143 {
144 Type type = allTypes[i];
145 if (type.IsClass && !type.IsAbstract &&
146 plugInInterface.IsAssignableFrom(type) &&
147 type.GetCustomAttribute(attributeType) != null)
148 {
149 plugInTypes.Add(type);
150 }
151 }
152 return plugInTypes;
153 }
154
159 [ExcludeFromCodeCoverage] // Indirectly tested by InjectPlugins
160 private static void HandleReplacingPlugIns(IEnumerable<Type> plugInTypes)
161 {
162 foreach (Type plugInType in plugInTypes)
163 {
164 IEnumerable<NanoXlsxPlugInAttribute> attributes = plugInType.GetCustomAttributes<NanoXlsxPlugInAttribute>();
165 foreach (NanoXlsxPlugInAttribute attribute in attributes)
166 {
167 if (plugInClasses.ContainsKey(attribute.PlugInUUID))
168 {
169 if (attribute.PlugInOrder <= plugInClasses[attribute.PlugInUUID].Order)
170 {
171 // Skip duplicates with lower order numbers
172 plugInClasses[attribute.PlugInUUID] = new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType);
173 }
174 }
175 else if (!plugInClasses.ContainsKey(attribute.PlugInUUID))
176 {
177 plugInClasses.Add(attribute.PlugInUUID, new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType));
178 }
179 }
180 }
181 }
182
187 private static void HandleQueuePlugIns(IEnumerable<Type> queuePlugInTypes)
188 {
189 foreach (Type plugInType in queuePlugInTypes)
190 {
191 IEnumerable<NanoXlsxQueuePlugInAttribute> attributes = plugInType.GetCustomAttributes<NanoXlsxQueuePlugInAttribute>();
192 foreach (var attribute in attributes)
193 {
194 if (!queuePlugInClasses.TryGetValue(attribute.QueueUUID, out var value))
195 {
196 value = new List<PlugInInstance>();
197 queuePlugInClasses.Add(attribute.QueueUUID, value);
198 }
199
200 value.Add(new PlugInInstance(attribute.PlugInUUID, attribute.PlugInOrder, plugInType));
201 }
202 }
203 // Sort each list based on PlugInOrder (ascending)
204 foreach (KeyValuePair<string, List<PlugInInstance>> entry in queuePlugInClasses)
205 {
206 entry.Value.Sort((a, b) => a.Order.CompareTo(b.Order));
207 }
208 }
209
219 internal static T GetPlugIn<T>(string plugInUUID, T fallBackInstance)
220 {
221 if (plugInClasses.TryGetValue(plugInUUID, out var plugIn))
222 {
223 return (T)Activator.CreateInstance(plugIn.Type);
224 }
225 else
226 {
227 return fallBackInstance;
228 }
229 }
230
241 internal static T GetNextQueuePlugIn<T>(string queueUUID, string lastPlugInUUID, out string currentPlugInUUID)
242 {
243 if (queuePlugInClasses.TryGetValue(queueUUID, out var plugInList) && plugInList.Count > 0)
244 {
245 PlugInInstance plugIn = null;
246 if (lastPlugInUUID == null)
247 {
248 plugIn = plugInList[0];
249 }
250 else
251 {
252 // Find the next plug-in after the currentPlugInUUID
253 int index = plugInList.FindIndex(p => p.UUID == lastPlugInUUID);
254 if (index >= 0 && index + 1 < plugInList.Count)
255 {
256 plugIn = plugInList[index + 1]; // Get the next plug-in in the list
257 }
258 }
259 if (plugIn == null)
260 {
261 currentPlugInUUID = null;
262 return default;
263 }
264 currentPlugInUUID = plugIn.UUID;
265 return (T)Activator.CreateInstance(plugIn.Type);
266 }
267 else
268 {
269 currentPlugInUUID = null;
270 return default;
271 }
272 }
273
277 private sealed class PlugInInstance
278 {
282 public string UUID { get; private set; }
286 public int Order { get; private set; }
291 public Type Type { get; private set; }
292
299 internal PlugInInstance(string uuid, int order, Type type)
300 {
301 this.UUID = uuid;
302 this.Order = order;
303 this.Type = type;
304 }
305 }
306 }
307}
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.