< Summary

Information
Class: Ice.PluginManagerI
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/PluginManagerI.cs
Tag: 71_18251537082
Line coverage
74%
Covered lines: 114
Uncovered lines: 40
Coverable lines: 154
Total lines: 420
Line coverage: 74%
Branch coverage
67%
Covered branches: 43
Total branches: 64
Branch coverage: 67.1%
Method coverage
93%
Covered methods: 14
Total methods: 15
Method coverage: 93.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
initializePlugins()83.33%6.42677.27%
getPlugins()0%620%
getPlugin(...)50%4.25475%
addPlugin(...)50%4.25475%
destroy()100%6.68673.33%
.ctor(...)100%11100%
loadPlugins()78.57%14.911483.33%
loadPlugin(...)54.17%37.772471.19%
findPlugin(...)100%44100%
.ctor(...)100%11100%
get_name()100%11100%
set_name(...)100%210%
get_plugin()100%11100%
set_plugin(...)100%210%
.ctor(...)100%210%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/PluginManagerI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Collections;
 6using System.Diagnostics;
 7
 8namespace Ice;
 9
 10/// <summary>
 11/// Applications implement this interface to provide a plug-in factory
 12/// to the Ice run time.
 13/// </summary>
 14public interface PluginFactory
 15{
 16    /// <summary>Gets the default and preferred name for plug-ins created by this factory.</summary>
 17    string pluginName { get; }
 18
 19    /// <summary>
 20    /// Called by the Ice run time to create a new plug-in.
 21    /// </summary>
 22    ///
 23    /// <param name="communicator">The communicator that is in the process of being initialized.</param>
 24    /// <param name="name">The name of the plug-in.</param>
 25    /// <param name="args">The arguments that are specified in the plug-ins configuration.</param>
 26    /// <returns>The plug-in that was created by this method.</returns>
 27    Plugin create(Communicator communicator, string name, string[] args);
 28}
 29
 30internal sealed class PluginManagerI : PluginManager
 31{
 32    private const string _kindOfObject = "plugin";
 33
 34    public void initializePlugins()
 35    {
 136        if (_initialized)
 37        {
 038            throw new InitializationException("Plug-ins already initialized.");
 39        }
 40
 41        //
 42        // Invoke initialize() on the plug-ins, in the order they were loaded.
 43        //
 144        var initializedPlugins = new ArrayList();
 45        try
 46        {
 147            foreach (PluginInfo p in _plugins)
 48            {
 49                try
 50                {
 151                    p.plugin.initialize();
 152                }
 053                catch (PluginInitializationException)
 54                {
 055                    throw;
 56                }
 157                catch (System.Exception ex)
 58                {
 159                    throw new PluginInitializationException($"Plugin '{p.name}' initialization failed.", ex);
 60                }
 161                initializedPlugins.Add(p.plugin);
 62            }
 163        }
 164        catch (System.Exception)
 65        {
 66            //
 67            // Destroy the plug-ins that have been successfully initialized, in the
 68            // reverse order.
 69            //
 170            initializedPlugins.Reverse();
 171            foreach (Plugin p in initializedPlugins)
 72            {
 73                try
 74                {
 175                    p.destroy();
 176                }
 077                catch (System.Exception)
 78                {
 79                    // Ignore.
 080                }
 81            }
 182            throw;
 83        }
 84
 185        _initialized = true;
 186    }
 87
 88    public string[] getPlugins()
 89    {
 090        lock (_mutex)
 91        {
 092            var names = new ArrayList();
 093            foreach (PluginInfo p in _plugins)
 94            {
 095                names.Add(p.name);
 96            }
 097            return (string[])names.ToArray(typeof(string));
 98        }
 099    }
 100
 101    public Plugin getPlugin(string name)
 102    {
 1103        lock (_mutex)
 104        {
 1105            if (_communicator is null)
 106            {
 0107                throw new CommunicatorDestroyedException();
 108            }
 109
 1110            Plugin? p = findPlugin(name);
 1111            if (p is not null)
 112            {
 1113                return p;
 114            }
 115
 0116            throw new NotRegisteredException(_kindOfObject, name);
 117        }
 1118    }
 119
 120    public void addPlugin(string name, Plugin plugin)
 121    {
 1122        lock (_mutex)
 123        {
 1124            if (_communicator is null)
 125            {
 0126                throw new CommunicatorDestroyedException();
 127            }
 128
 1129            if (findPlugin(name) is not null)
 130            {
 0131                throw new AlreadyRegisteredException(_kindOfObject, name);
 132            }
 133
 1134            _plugins.Add(new PluginInfo(name, plugin));
 1135        }
 1136    }
 137
 138    public void destroy()
 139    {
 1140        lock (_mutex)
 141        {
 1142            if (_communicator is not null)
 143            {
 1144                if (_initialized)
 145                {
 1146                    var plugins = (ArrayList)_plugins.Clone();
 1147                    plugins.Reverse();
 1148                    foreach (PluginInfo p in plugins)
 149                    {
 150                        try
 151                        {
 1152                            p.plugin.destroy();
 1153                        }
 0154                        catch (System.Exception ex)
 155                        {
 0156                            Util.getProcessLogger().warning("unexpected exception raised by plug-in `" +
 0157                                                            p.name + "' destruction:\n" + ex.ToString());
 0158                        }
 159                    }
 160                }
 161
 1162                _communicator = null;
 163            }
 1164        }
 1165    }
 166
 1167    internal PluginManagerI(Communicator communicator)
 168    {
 1169        _communicator = communicator;
 1170        _plugins = new ArrayList();
 1171        _initialized = false;
 1172    }
 173
 174    internal void loadPlugins()
 175    {
 176        Debug.Assert(_communicator is not null);
 1177        string prefix = "Ice.Plugin.";
 1178        Properties properties = _communicator.getProperties();
 1179        Dictionary<string, string> plugins = properties.getPropertiesForPrefix(prefix);
 180
 181        // First, create plug-ins using the plug-in factories from initData, in order.
 1182        foreach (PluginFactory pluginFactory in _communicator.instance.initializationData().pluginFactories)
 183        {
 1184            string name = pluginFactory.pluginName;
 1185            string key = $"Ice.Plugin.{name}";
 1186            if (plugins.TryGetValue(key, out string? pluginSpec))
 187            {
 0188                loadPlugin(pluginFactory, name, pluginSpec);
 0189                plugins.Remove(key);
 190            }
 191            else
 192            {
 1193                loadPlugin(pluginFactory, name, "");
 194            }
 195        }
 196
 197        //
 198        // Load and initialize the plug-ins defined in the property set
 199        // with the prefix "Ice.Plugin.". These properties should
 200        // have the following format:
 201        //
 202        // Ice.Plugin.<name>=entry_point [args]
 203        //
 204        // The code below is different from the Java/C++ algorithm
 205        // because C# must support full assembly names such as:
 206        //
 207        // Ice.Plugin.Logger=logger, Version=0.0.0.0, Culture=neutral:LoginPluginFactory
 208        //
 209        // If the Ice.PluginLoadOrder property is defined, load the
 210        // specified plug-ins in the specified order, then load any
 211        // remaining plug-ins.
 212        //
 213
 1214        string[] loadOrder = properties.getIcePropertyAsList("Ice.PluginLoadOrder");
 1215        for (int i = 0; i < loadOrder.Length; ++i)
 216        {
 1217            if (loadOrder[i].Length == 0)
 218            {
 219                continue;
 220            }
 221
 1222            if (findPlugin(loadOrder[i]) is not null)
 223            {
 0224                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' already loaded.");
 225            }
 226
 1227            string key = $"Ice.Plugin.{loadOrder[i]}";
 1228            plugins.TryGetValue(key, out string? value);
 1229            if (value is not null)
 230            {
 1231                loadPlugin(null, loadOrder[i], value);
 1232                plugins.Remove(key);
 233            }
 234            else
 235            {
 0236                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' not defined.");
 237            }
 238        }
 239
 240        //
 241        // Load any remaining plug-ins that weren't specified in PluginLoadOrder.
 242        //
 1243        foreach (KeyValuePair<string, string> entry in plugins)
 244        {
 1245            loadPlugin(null, entry.Key[prefix.Length..], entry.Value);
 246        }
 1247    }
 248
 249    private void loadPlugin(PluginFactory? pluginFactory, string name, string pluginSpec)
 250    {
 251        Debug.Assert(_communicator is not null);
 252
 1253        if (findPlugin(name) is not null)
 254        {
 0255            throw new AlreadyRegisteredException(_kindOfObject, name);
 256        }
 257
 1258        string[] args = [];
 1259        string entryPoint = "";
 1260        if (pluginSpec.Length > 0)
 261        {
 262            //
 263            // Split the entire property value into arguments. An entry point containing spaces
 264            // must be enclosed in quotes.
 265            //
 266            try
 267            {
 1268                args = Ice.UtilInternal.Options.split(pluginSpec);
 1269            }
 0270            catch (ParseException ex)
 271            {
 0272                throw new PluginInitializationException($"Invalid arguments for plug-in '{name}'.", ex);
 273            }
 274
 275            Debug.Assert(args.Length > 0);
 276
 1277            entryPoint = args[0];
 278
 279            //
 280            // Shift the arguments.
 281            //
 1282            string[] tmp = new string[args.Length - 1];
 1283            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 1284            args = tmp;
 285
 286            // Convert command-line options into properties.
 1287            Properties properties = _communicator.getProperties();
 1288            args = properties.parseCommandLineOptions(name, args);
 289        }
 290
 1291        string err = "unable to load plug-in `" + entryPoint + "': ";
 292
 1293        if (pluginFactory is null)
 294        {
 295            //
 296            // Extract the assembly name and the class name.
 297            //
 1298            int sepPos = entryPoint.IndexOf(':', StringComparison.Ordinal);
 1299            if (sepPos != -1)
 300            {
 301                const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 1302                if (entryPoint.Length > 3 &&
 1303                   sepPos == 1 &&
 1304                   driveLetters.Contains(entryPoint[0], StringComparison.Ordinal) &&
 1305                   (entryPoint[2] == '\\' || entryPoint[2] == '/'))
 306                {
 0307                    sepPos = entryPoint.IndexOf(':', 3);
 308                }
 309            }
 1310            if (sepPos == -1)
 311            {
 0312                throw new PluginInitializationException($"{err}invalid entry point format");
 313            }
 314
 1315            string assemblyName = entryPoint[..sepPos];
 1316            string className = entryPoint[(sepPos + 1)..];
 317
 318            System.Reflection.Assembly? pluginAssembly;
 319            try
 320            {
 321                //
 322                // First try to load the assembly using Assembly.Load, which will succeed
 323                // if a fully-qualified name is provided or if a partial name has been qualified
 324                // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed
 325                // if a file name is configured or a partial name is configured and DEVPATH is used.
 326                //
 327                // We catch System.Exception as this can fail with System.ArgumentNullException
 328                // or System.IO.IOException depending of the .NET framework and platform.
 329                //
 330                try
 331                {
 1332                    pluginAssembly = System.Reflection.Assembly.Load(assemblyName);
 1333                }
 1334                catch (System.Exception ex)
 335                {
 336                    try
 337                    {
 1338                        pluginAssembly = System.Reflection.Assembly.LoadFrom(assemblyName);
 1339                    }
 0340                    catch (System.IO.IOException)
 341                    {
 342#pragma warning disable CA2200 // Rethrow to preserve stack details
 0343                        throw ex;
 344#pragma warning restore CA2200 // Rethrow to preserve stack details
 345                    }
 1346                }
 1347            }
 0348            catch (System.Exception ex)
 349            {
 0350                throw new PluginInitializationException($"{err}unable to load assembly '{assemblyName}'.", ex);
 351            }
 352
 353            //
 354            // Instantiate the class.
 355            //
 356            Type? c;
 357            try
 358            {
 1359                c = pluginAssembly.GetType(className, true);
 1360            }
 0361            catch (System.Exception ex)
 362            {
 0363                throw new PluginInitializationException($"{err}GetType failed for '{className}'.", ex);
 364            }
 365
 366            try
 367            {
 1368                pluginFactory = (PluginFactory)Ice.Internal.AssemblyUtil.createInstance(c);
 1369                if (pluginFactory is null)
 370                {
 0371                    throw new PluginInitializationException($"{err}can't find constructor for '{className}'.");
 372                }
 1373            }
 0374            catch (System.Exception ex)
 375            {
 0376                throw new PluginInitializationException($"{err}SystemException", ex);
 377            }
 378        }
 379
 380        Plugin? plugin;
 381        try
 382        {
 1383            plugin = pluginFactory.create(_communicator, name, args);
 1384        }
 1385        catch (PluginInitializationException)
 386        {
 1387            throw;
 388        }
 0389        catch (System.Exception ex)
 390        {
 0391            throw new PluginInitializationException($"{err}System.Exception in factory.create", ex);
 392        }
 393
 1394        if (plugin is null)
 395        {
 0396            throw new PluginInitializationException($"{err}factory.create returned null plug-in");
 397        }
 398
 1399        _plugins.Add(new PluginInfo(name, plugin));
 1400    }
 401
 402    private Plugin? findPlugin(string name)
 403    {
 1404        foreach (PluginInfo p in _plugins)
 405        {
 1406            if (name.Equals(p.name, StringComparison.Ordinal))
 407            {
 1408                return p.plugin;
 409            }
 410        }
 1411        return null;
 1412    }
 413
 1414    internal record class PluginInfo(string name, Plugin plugin);
 415
 416    private Communicator? _communicator;
 417    private readonly ArrayList _plugins;
 418    private bool _initialized;
 1419    private readonly object _mutex = new();
 420}