Class PluginLoader

java.lang.Object
org.egothor.methodatlas.command.PluginLoader

public final class PluginLoader extends Object
Resolves and configures discovery plugins via ServiceLoader.

Each plugin JAR ships a service registration file under META-INF/services/ listing its implementation of TestDiscovery (and, optionally, SourcePatcher). This loader walks the classpath, instantiates every registered provider, applies the run-time TestDiscoveryConfig via configure, and verifies that every provider declares a unique pluginId().

Lifecycle

Instances are intended to be created once per CLI run and injected into the Command implementations that need them. The loader itself is stateless — no instance fields — so a single loader can be shared between commands that participate in the same orchestration. The lifecycle of the loaded providers is owned by the caller: a typical usage closes them in a finally block via closeAll(List).

Thread safety

This class is thread-safe. ServiceLoader.load(Class) resolution is idempotent per classloader, and no shared mutable state is maintained.

Since:
1.0.0
See Also:
  • TestDiscovery
  • SourcePatcher
  • Command
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates a new plugin loader.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    closeAll(List<org.egothor.methodatlas.api.TestDiscovery> providers)
    Closes every provider in the list, logging any IOException at Level.FINE and continuing so that all providers are attempted.
    List<org.egothor.methodatlas.api.SourcePatcher>
    loadPatchers(org.egothor.methodatlas.api.TestDiscoveryConfig config)
    Loads all SourcePatcher providers registered via ServiceLoader, configures each one with config, and returns them in registration order.
    List<org.egothor.methodatlas.api.TestDiscovery>
    loadProviders(org.egothor.methodatlas.api.TestDiscoveryConfig config)
    Loads all TestDiscovery providers registered via ServiceLoader, configures each one with config, and returns them in registration order.
    static void
    requireUniqueDiscoveryIds(List<org.egothor.methodatlas.api.TestDiscovery> providers)
    Verifies that every TestDiscovery provider in the list has a unique TestDiscovery.pluginId().
    static void
    requireUniquePatcherIds(List<org.egothor.methodatlas.api.SourcePatcher> patchers)
    Verifies that every SourcePatcher in the list has a unique SourcePatcher.pluginId().

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • PluginLoader

      public PluginLoader()
      Creates a new plugin loader. The loader carries no instance state and is safe to share across commands within a single CLI run.
  • Method Details

    • loadProviders

      public List<org.egothor.methodatlas.api.TestDiscovery> loadProviders(org.egothor.methodatlas.api.TestDiscoveryConfig config)
      Loads all TestDiscovery providers registered via ServiceLoader, configures each one with config, and returns them in registration order.

      The returned providers are open resources. Callers must close them through closeAll(List) in a finally block to release any per-provider resources (file handles, native processes, etc.).

      Time complexity is O(p) in the number of providers; the ServiceLoader lookup itself is dominated by classpath scanning.

      Parameters:
      config - run-time configuration forwarded to every provider via TestDiscovery.configure(org.egothor.methodatlas.api.TestDiscoveryConfig); must not be null
      Returns:
      non-empty list of configured providers in registration order
      Throws:
      IllegalStateException - if no providers are found on the classpath, or if two providers share the same TestDiscovery.pluginId()
    • loadPatchers

      public List<org.egothor.methodatlas.api.SourcePatcher> loadPatchers(org.egothor.methodatlas.api.TestDiscoveryConfig config)
      Loads all SourcePatcher providers registered via ServiceLoader, configures each one with config, and returns them in registration order.

      Unlike loadProviders(org.egothor.methodatlas.api.TestDiscoveryConfig), returning an empty list is legitimate: languages that do not support source write-back (such as TypeScript or Python) ship no patcher.

      Parameters:
      config - run-time configuration forwarded to every patcher via SourcePatcher.configure(org.egothor.methodatlas.api.TestDiscoveryConfig); must not be null
      Returns:
      possibly-empty list of configured patchers in registration order
      Throws:
      IllegalStateException - if two patchers share the same SourcePatcher.pluginId()
    • closeAll

      public void closeAll(List<org.egothor.methodatlas.api.TestDiscovery> providers)
      Closes every provider in the list, logging any IOException at Level.FINE and continuing so that all providers are attempted.

      This method never throws: a provider whose close fails leaves its resources in an indeterminate state, but the orchestration layer always exits cleanly. Failures are observable through the FINE-level log.

      Parameters:
      providers - list of providers to close; must not be null
    • requireUniqueDiscoveryIds

      public static void requireUniqueDiscoveryIds(List<org.egothor.methodatlas.api.TestDiscovery> providers)
      Verifies that every TestDiscovery provider in the list has a unique TestDiscovery.pluginId().

      This method is static because it is a pure validation with no instance dependencies — test code calls it directly with handcrafted provider lists, and the instance loader calls it after a ServiceLoader sweep. Time complexity is O(p) in the number of providers.

      Parameters:
      providers - list of providers to validate; must not be null
      Throws:
      IllegalStateException - if two or more providers share the same id
    • requireUniquePatcherIds

      public static void requireUniquePatcherIds(List<org.egothor.methodatlas.api.SourcePatcher> patchers)
      Verifies that every SourcePatcher in the list has a unique SourcePatcher.pluginId().
      Parameters:
      patchers - list of patchers to validate; must not be null
      Throws:
      IllegalStateException - if two or more patchers share the same id