All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.managers.plugin.PluginManager Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.managers.plugin;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.jdk.Objects;
import com.alee.managers.plugin.data.*;
import com.alee.utils.*;
import com.alee.utils.collection.ImmutableList;
import com.alee.utils.compare.Filter;
import com.alee.utils.sort.TopologicalGraphProvider;
import com.alee.utils.sort.TopologicalSorter;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * {@link PluginManager} for any {@link Plugin} of specific type.
 *
 * @param 

{@link Plugin} type * @author Mikle Garin * @see How to use PluginManager * @see Plugin */ public abstract class PluginManager

{ /** * todo 1. Replace {@link #applyInitializationStrategy()} this with a custom comparator for listener events */ /** * {@link FileFilter} used to filter out directories only. */ @NotNull protected static final FileFilter DIRECTORIES_FILTER = new FileFilter () { @Override public boolean accept ( @NotNull final File file ) { return file.isDirectory (); } }; /** * Plugin checks lock object. */ @NotNull protected final Object checkLock; /** * Global class loader for all plugin managers implementations. * * @see #getGlobalClassLoader() */ @Nullable protected static PluginClassLoader globalClassLoader; /** * Local class loader for this specific plugin manager implementation. * * @see #getLocalClassLoader() */ @Nullable protected PluginClassLoader localClassLoader; /** * Plugins listeners. */ @NotNull protected final List> listeners; /** * Related plugin managers list. * These managers are used to check dependencies load state and some other information later on. */ @NotNull protected final List parentManagers; /** * Detected plugins list. * All plugins with available descriptions will get into this list. */ @NotNull protected final List> detectedPlugins; /** * Detected plugins cached by plugin file path. */ @NotNull protected final Map> detectedPluginsByPath; /** * Recently detected plugins list. * Contains plugins detected while last plugins check. */ @NotNull protected final List> recentlyDetected; /** * Recently initialized plugins list. * Contains plugins initialized while last plugins check. */ @NotNull protected final List

recentlyInitialized; /** * Loaded and running plugins list. * This might be less than list of detected plugins in the end due to lots of different reasons. * Only those plugins which are actually loaded successfully are getting added here. */ @NotNull protected final List

availablePlugins; /** * Map of plugins cached by their IDs. */ @NotNull protected final Map availablePluginsById; /** * Map of plugins cached by their classes. */ @NotNull protected final Map, P> availablePluginsByClass; /** * Special filter to filter out unwanted plugins before their initialization. * It is up to developer to specify this filter and its conditions. */ @Nullable protected Filter> pluginFilter; /** * Whether should allow loading multiple plugins with the same ID or not. * In case this is set to false only the newest version of the same plugin will be loaded if more than one provided. */ protected boolean allowSimilarPlugins; /** * Plugins directory path. * It is either absolute path or relative to working directory path. */ @Nullable protected String pluginsDirectoryPath; /** * Whether plugins directory subfolders should be checked recursively or not. */ protected boolean checkRecursively; /** * Plugin directory files filter. * By default "*.jar" and "*.plugin" files are accepted. */ @Nullable protected FileFilter fileFilter; /** * Class loader type used by this manager to load plugins. * Be aware that different types might have a heavy impact on classes availability across your application. */ @NotNull protected ClassLoaderType classLoaderType; /** * Whether or not {@link PluginManager} should provide a fallback {@link ClassLoader} in cases when classpath of the * {@link ClassLoader} retrieved according to the specified {@link ClassLoaderType} cannot be modified in runtime. */ protected boolean provideClassLoaderFallback; /** * Constructs new plugin manager. */ public PluginManager () { this ( null ); } /** * Constructs new plugin manager. * * @param pluginsDirectoryPath plugins directory path */ public PluginManager ( @Nullable final String pluginsDirectoryPath ) { this ( pluginsDirectoryPath, true ); } /** * Constructs new plugin manager. * * @param pluginsDirectoryPath plugins directory path * @param recursively whether plugins directory subfolders should be checked recursively or not */ public PluginManager ( @Nullable final String pluginsDirectoryPath, final boolean recursively ) { // Various runtime variables this.checkLock = new Object (); this.listeners = new ArrayList> ( 1 ); this.parentManagers = new ArrayList (); // Default settings this.pluginFilter = null; this.allowSimilarPlugins = false; this.classLoaderType = ClassLoaderType.context; this.provideClassLoaderFallback = false; // User settings this.pluginsDirectoryPath = pluginsDirectoryPath; this.checkRecursively = recursively; // Default file filter this.fileFilter = new FileFilter () { @Override public boolean accept ( final File file ) { final String name = file.getName ().toLowerCase ( Locale.ROOT ); return name.endsWith ( ".jar" ) || name.endsWith ( ".plugin" ); } }; // Runtime variables this.detectedPlugins = new ArrayList> (); this.detectedPluginsByPath = new HashMap> (); this.recentlyDetected = new ArrayList> (); this.recentlyInitialized = new ArrayList

(); this.availablePlugins = new ArrayList

(); this.availablePluginsById = new HashMap (); this.availablePluginsByClass = new HashMap, P> (); // Plugin manager classes aliases XmlUtils.processAnnotations ( PluginInformation.class ); XmlUtils.processAnnotations ( PluginVersion.class ); XmlUtils.processAnnotations ( PluginDependency.class ); XmlUtils.processAnnotations ( PluginLibrary.class ); } /** * Returns plugins directory path. * * @return plugins directory path */ @Nullable public String getPluginsDirectoryPath () { return pluginsDirectoryPath; } /** * Sets plugins directory path. * * @param path new plugins directory path */ public void setPluginsDirectoryPath ( @Nullable final String path ) { this.pluginsDirectoryPath = path; } /** * Returns whether plugins directory subfolders should be checked recursively or not. * * @return true if plugins directory subfolders should be checked recursively, false otherwise */ public boolean isCheckRecursively () { return checkRecursively; } /** * Sets whether plugins directory subfolders should be checked recursively or not. * * @param checkRecursively whether plugins directory subfolders should be checked recursively or not */ public void setCheckRecursively ( final boolean checkRecursively ) { this.checkRecursively = checkRecursively; } /** * Returns plugins directory file filter. * * @return plugins directory file filter */ @Nullable public FileFilter getFileFilter () { return fileFilter; } /** * Sets plugins directory file filter. * Note that setting this filter will not have any effect on plugins which are already initialized. * * @param filter plugins directory file filter */ public void setFileFilter ( @Nullable final FileFilter filter ) { this.fileFilter = filter; } /** * Returns class loader type used by this manager to load plugins. * * @return class loader type used by this manager to load plugins */ @NotNull public ClassLoaderType getClassLoaderType () { return classLoaderType; } /** * Sets class loader type used by this manager to load plugins. * * @param classLoaderType class loader type used by this manager to load plugins */ public void setClassLoaderType ( @NotNull final ClassLoaderType classLoaderType ) { this.classLoaderType = classLoaderType; } /** * Returns whether or not {@link PluginManager} should provide a fallback {@link ClassLoader} in cases when classpath of the * {@link ClassLoader} retrieved according to the specified {@link ClassLoaderType} cannot be modified in runtime. * * @return {@code true} if {@link PluginManager} should provide a fallback {@link ClassLoader} when needed, {@code false} otherwise */ public boolean isProvideClassLoaderFallback () { return provideClassLoaderFallback; } /** * Sets whether or not {@link PluginManager} should provide a fallback {@link ClassLoader} in cases when classpath of the * {@link ClassLoader} retrieved according to the specified {@link ClassLoaderType} cannot be modified in runtime. * * @param provideClassLoaderFallback whether or not {@link PluginManager} should provide a fallback {@link ClassLoader} when needed */ public void setProvideClassLoaderFallback ( final boolean provideClassLoaderFallback ) { this.provideClassLoaderFallback = provideClassLoaderFallback; } /** * Returns special filter that filters out unwanted plugins before their initialization. * * @return special filter that filters out unwanted plugins before their initialization */ @Nullable public Filter> getPluginFilter () { return pluginFilter; } /** * Sets special filter that filters out unwanted plugins before their initialization. * * @param pluginFilter special filter that filters out unwanted plugins before their initialization */ public void setPluginFilter ( @Nullable final Filter> pluginFilter ) { this.pluginFilter = pluginFilter; } /** * Returns whether should allow loading multiple plugins with the same ID or not. * * @return true if should allow loading multiple plugins with the same ID, false otherwise */ public boolean isAllowSimilarPlugins () { return allowSimilarPlugins; } /** * Sets whether should allow loading multiple plugins with the same ID or not. * * @param allow whether should allow loading multiple plugins with the same ID or not */ public void setAllowSimilarPlugins ( final boolean allow ) { this.allowSimilarPlugins = allow; } /** * Returns name of the plugin descriptor file. * This file should contain serialized PluginInformation. * * @return name of the plugin descriptor file */ @NotNull protected String getPluginDescriptorFile () { return "plugin.xml"; } /** * Returns name of the plugin logo file. * Logo should be placed near the plugin descriptor file. * * @return name of the plugin logo file */ @Nullable protected String getPluginLogoFile () { return "logo.png"; } /** * Returns plugin type accepted by this {@link PluginManager}. * In case {@code null} is returned this manager accepts any plugin type. * * @return plugin type accepted by this {@link PluginManager} */ @Nullable protected String getAcceptedPluginType () { return null; } /** * Returns {@link List} of parent {@link PluginManager}s. * * @return {@link List} of parent {@link PluginManager}s */ @NotNull public List getParentManagers () { synchronized ( parentManagers ) { return CollectionUtils.copy ( parentManagers ); } } /** * Returns whether or not specified {@link PluginManager} is added as a parent manager for this one. * This will also ask all parent {@link PluginManager}s whether they have specified one as a parent. * This method is defended against circular parent references, but it is not recommended to have such. * * @param manager {@link PluginManager} to check * @return {@code true} if specified {@link PluginManager} is added as a parent manager for this one, {@code false} otherwise */ public boolean isParentManager ( @NotNull final PluginManager manager ) { return isParentManager ( manager, new HashSet ( 3 ) ); } /** * Returns whether or not specified {@link PluginManager} is added as a parent manager for this one. * This will also ask all parent {@link PluginManager}s whether they have specified one as a parent. * This method is defended against circular parent references, but it is not recommended to have such. * * @param manager {@link PluginManager} to check * @param checked {@link Set} of checked {@link PluginManager}s * @return {@code true} if specified {@link PluginManager} is added as a parent manager for this one, {@code false} otherwise */ protected boolean isParentManager ( @NotNull final PluginManager manager, @NotNull final Set checked ) { synchronized ( parentManagers ) { // Saving this manager into checked set checked.add ( this ); // Checking parent managers boolean parent = false; for ( final PluginManager parentManager : parentManagers ) { // If one of the parent managers is the specified one we return true // If one of the parent managers was not checked yet and has the specified one we also return true if ( parentManager == manager || !checked.contains ( parentManager ) && parentManager.isParentManager ( manager, checked ) ) { parent = true; break; } } return parent; } } /** * Adds parent {@link PluginManager} reference. * Parent {@link PluginManager}s are used to check dependencies load state and some other information. * * @param manager {@link PluginManager} to add into parent managers {@link List} */ public void addParentManager ( @NotNull final PluginManager manager ) { synchronized ( parentManagers ) { if ( !parentManagers.contains ( manager ) ) { if ( manager.isParentManager ( PluginManager.this ) ) { LoggerFactory.getLogger ( PluginManager.class ).error ( String.format ( "%s already have %s as parent", ReflectUtils.getClassName ( manager.getClass () ), ReflectUtils.getClassName ( PluginManager.this.getClass () ) ) ); } parentManagers.add ( manager ); } else { LoggerFactory.getLogger ( PluginManager.class ).error ( String.format ( "%s was already added as a parent", ReflectUtils.getClassName ( manager.getClass () ) ) ); } } } /** * Removes parent {@link PluginManager} reference. * These {@link PluginManager}s are used to check dependencies load state and some other information later on. * * @param manager {@link PluginManager} to remove from parent managers {@link List} */ public void removeParentManager ( @NotNull final PluginManager manager ) { synchronized ( parentManagers ) { if ( parentManagers.contains ( manager ) ) { parentManagers.remove ( manager ); } else { LoggerFactory.getLogger ( PluginManager.class ).error ( String.format ( "%s was not added as a parent", ReflectUtils.getClassName ( manager.getClass () ) ) ); } } } /** * Registers specified {@link Plugin} within this {@link PluginManager}. * It will also create {@link DetectedPlugin} data based on provided information. * * @param plugin plugin to register */ public void registerPlugin ( @NotNull final P plugin ) { registerPlugin ( plugin, plugin.getInformation (), !SystemUtils.isHeadlessEnvironment () ? plugin.getLogo () : null ); } /** * Registers specified {@link Plugin} within this {@link PluginManager}. * It will also create {@link DetectedPlugin} data based on provided information. * * @param plugin plugin to register * @param information about this plugin * @param logo plugin logo */ public void registerPlugin ( @NotNull final P plugin, @NotNull final PluginInformation information, @Nullable final Icon logo ) { synchronized ( checkLock ) { final String logPrefix = "[" + information + "] "; LoggerFactory.getLogger ( PluginManager.class ).info ( logPrefix + "Initializing pre-loaded plugin" ); // Creating base detected plugin information final DetectedPlugin

detectedPlugin = new DetectedPlugin

( null, null, information, logo ); detectedPlugin.setStatus ( PluginStatus.loaded ); detectedPlugin.setPlugin ( plugin ); plugin.setPluginManager ( PluginManager.this ); plugin.setDetectedPlugin ( detectedPlugin ); // Saving plugin detectedPlugins.add ( detectedPlugin ); availablePlugins.add ( plugin ); availablePluginsById.put ( plugin.getId (), plugin ); availablePluginsByClass.put ( plugin.getClass (), plugin ); // Adding single initialized plugin recentlyInitialized.clear (); recentlyInitialized.add ( plugin ); // Sorting plugins according to their initialization strategies // todo Probably should optimize this and only sort upon retrieval applyInitializationStrategy (); LoggerFactory.getLogger ( PluginManager.class ).info ( logPrefix + "Pre-loaded plugin initialized" ); // Informing everyone about plugin registration firePluginsInitialized ( recentlyInitialized ); } } /** * Downloads plugin from the specified URL and tries to load it. * In case the file is not a plugin it will simply be ignored. * Plugins added this way will also be filtered and checked for other means. * * @param pluginFileURL plugin file URL */ public void scanPlugin ( @NotNull final URL pluginFileURL ) { try { final String url = pluginFileURL.toURI ().toASCIIString (); final File tmpFile = File.createTempFile ( "plugin", ".jar" ); final File downloadedPlugin = FileUtils.downloadFile ( url, tmpFile ); scanPlugin ( downloadedPlugin ); } catch ( final Exception e ) { LoggerFactory.getLogger ( PluginManager.class ).error ( "Unable to download plugin from URL: " + pluginFileURL, e ); } } /** * Tries to load plugin from the specified file. * In case the file is not a plugin it will simply be ignored. * Plugins added this way will also be filtered and checked for other means. * * @param pluginFile plugin file path */ public void scanPlugin ( @NotNull final String pluginFile ) { scanPlugin ( new File ( pluginFile ) ); } /** * Tries to load plugin from the specified file. * In case the file is not a plugin it will simply be ignored. * Plugins added this way will also be filtered and checked for other means. * * @param pluginFile plugin file */ public void scanPlugin ( @NotNull final File pluginFile ) { synchronized ( checkLock ) { final String scanPath = FileUtils.canonicalPath ( pluginFile ); final String msg = "Scanning plugin file: %s"; LoggerFactory.getLogger ( PluginManager.class ).info ( String.format ( msg, scanPath ) ); // Resetting recently detected plugins list recentlyDetected.clear (); // Collecting plugins information if ( collectPluginInformation ( pluginFile ) ) { // Initializing detected plugins initializeDetectedPlugins (); } } } /** * Performs plugins search within the specified plugins directory. * This call might be performed as many times as you like. * It will simply ignore plugins detected before and will process newly found plugins appropriately. */ public void scanPluginsDirectory () { scanPluginsDirectory ( getPluginsDirectoryPath (), isCheckRecursively () ); } /** * Performs plugins search within the specified plugins directory. * This call might be performed as many times as you like. * It will simply ignore plugins detected before and will process newly found plugins appropriately. * * @param recursively whether plugins directory subfolders should be checked recursively or not */ public void scanPluginsDirectory ( final boolean recursively ) { scanPluginsDirectory ( getPluginsDirectoryPath (), recursively ); } /** * Performs plugins search within the specified plugins directory. * This call might be performed as many times as you like. * It will simply ignore plugins detected before and will process newly found plugins appropriately. * * @param pluginsDirectoryPath plugins directory path */ public void scanPluginsDirectory ( @Nullable final String pluginsDirectoryPath ) { scanPluginsDirectory ( pluginsDirectoryPath, isCheckRecursively () ); } /** * Performs plugins search within the specified plugins directory. * This call might be performed as many times as you like. * It will simply ignore plugins detected before and will process newly found plugins appropriately. * * @param pluginsDirectoryPath plugins directory path * @param recursively whether plugins directory subfolders should be checked recursively or not */ public void scanPluginsDirectory ( @Nullable final String pluginsDirectoryPath, final boolean recursively ) { synchronized ( checkLock ) { // Ignore check if check path is not specified if ( pluginsDirectoryPath != null ) { // Informing about plugins check start firePluginsCheckStarted ( pluginsDirectoryPath, recursively ); // Resetting recently detected plugins list recentlyDetected.clear (); // Collecting plugins information collectPluginsInformation ( pluginsDirectoryPath, recursively ); // Initializing detected plugins initializeDetectedPlugins (); // Informing about plugins check end firePluginsCheckEnded ( pluginsDirectoryPath, recursively ); } } } /** * Collects information about available plugins. * * @param dir plugins directory path * @param recursively whether plugins directory subfolders should be checked recursively or not */ protected void collectPluginsInformation ( @NotNull final String dir, final boolean recursively ) { collectPluginsInformationImpl ( new File ( dir ), recursively ); sortRecentlyDetectedPluginsByDependencies (); } /** * Collects information about available plugins. * * @param dir plugins directory * @param recursively whether plugins directory subfolders should be checked recursively or not */ protected void collectPluginsInformationImpl ( @NotNull final File dir, final boolean recursively ) { LoggerFactory.getLogger ( PluginManager.class ).info ( String.format ( "Scanning plugins directory (%s): %s", recursively ? "recursive" : "flat", getPluginsDirectoryPath () ) ); // Checking all files final File[] files = dir.listFiles ( getFileFilter () ); if ( files != null ) { for ( final File file : files ) { collectPluginInformation ( file ); } } // Checking sub-directories recursively if ( recursively ) { // todo Once moved to NIO this filter should be changed, right now it might not accept drives and custom native stuff // todo DirectoriesFilter usage was removed due to FileSystemView usage and related calls final File[] subfolders = dir.listFiles ( DIRECTORIES_FILTER ); if ( subfolders != null ) { for ( final File subfolder : subfolders ) { collectPluginsInformationImpl ( subfolder, true ); } } } } /** * Tries to collect plugin information from the specified file. * This call will simply be ignored if this is not a plugin file or if something goes wrong. * * @param file plugin file to process * @return true if operation succeeded, false otherwise */ protected boolean collectPluginInformation ( @NotNull final File file ) { final boolean result; final DetectedPlugin

plugin = detectPlugin ( file ); if ( plugin != null ) { recentlyDetected.add ( plugin ); final String msg = "Plugin detected: %s"; LoggerFactory.getLogger ( PluginManager.class ).info ( String.format ( msg, plugin ) ); result = true; } else { result = false; } return result; } /** * Tries to sort recently detected plugins list by known plugin dependencies. * This sorting will have effect only if dependencies are pointing at plugins of the same type. *

* In case you setup dependencies on other type of plugin you will have to manually check whether those are loaded or not. * That can be done by setting plugin filter into this manager and checking dependencies there. */ protected void sortRecentlyDetectedPluginsByDependencies () { if ( recentlyDetected.size () > 1 ) { try { LoggerFactory.getLogger ( PluginManager.class ).info ( "Sorting detected plugins according to known dependencies" ); // Collecting plugins that doesn't have any dependencies or their dependencies are loaded // Also mapping dependencies for quick access later final int s = recentlyDetected.size (); final List> root = new ArrayList> ( s ); final Map>> references = new HashMap>> (); for ( final DetectedPlugin

plugin : recentlyDetected ) { final List dependencies = plugin.getInformation ().getDependencies (); if ( CollectionUtils.notEmpty ( dependencies ) ) { // List of resulting dependencies // There could be less resulting dependenies due to missing optional ones // It will also not contain any dependencies that are already loaded prior to this operation final List dependencyIds = new ArrayList ( dependencies.size () ); // List of missing dependencies final List missingDependencies = new ArrayList ( 1 ); // Iterating through detected plugin dependencies boolean allDependenciesLoaded = true; for ( final PluginDependency dependency : dependencies ) { // Checking that dependency is not yet loaded if ( !isAvailable ( dependency ) ) { // Checking whether dependency is in detected list if ( isDetected ( dependency ) ) { // Dependency is not loaded when its not in available list allDependenciesLoaded = false; // Saving resulting dependency ID dependencyIds.add ( dependency.getPluginId () ); } else if ( !dependency.isOptional () ) { // Plugin cannot be loaded due to missing required dependency missingDependencies.add ( dependency ); } } } // Continue only if plugin can be loaded if ( CollectionUtils.isEmpty ( missingDependencies ) ) { // Mapping all dependencies of this plugin if ( CollectionUtils.notEmpty ( dependencyIds ) ) { for ( final String dependencyId : dependencyIds ) { List> dependent = references.get ( dependencyId ); if ( dependent == null ) { dependent = new ArrayList> ( 1 ); references.put ( dependencyId, dependent ); } dependent.add ( plugin ); } } // Adding plugin into root plugins list if its dependencies are met if ( allDependenciesLoaded ) { root.add ( plugin ); } } } else { // Adding plugin into root plugins list as it doesn't have any dependencies root.add ( plugin ); } } // Creating graph provider for further topological sorting final TopologicalGraphProvider> graph = new TopologicalGraphProvider> () { @Override public List> getRoots () { return root; } @Override public List> getChildren ( final DetectedPlugin

parent ) { final List> children = references.get ( parent.getInformation ().getId () ); return children != null ? children : Collections.EMPTY_LIST; } }; // Performing topological sorting // Saving result as new recently detected plugins list final List> sorted = new TopologicalSorter> ( graph ).list (); // Adding plugins which didn't get into graph into the end // There might be plugins with some side dependencies and they might still be properly initialized // Plugins that have missing dependencies will also be added into this list and taken care of later for ( final DetectedPlugin

plugin : recentlyDetected ) { if ( !sorted.contains ( plugin ) ) { sorted.add ( plugin ); } } // Replacing detected list with it's sorted version recentlyDetected.clear (); recentlyDetected.addAll ( sorted ); } catch ( final Exception e ) { LoggerFactory.getLogger ( PluginManager.class ).error ( "Unable to perform proper dependencies sorting", e ); } } } /** * Returns whether or not {@link PluginDependency} is available in this or any parent {@link PluginManager}. * * @param dependency {@link PluginDependency} to look for * @return {@code true} if {@link PluginDependency} is available in this or any parent {@link PluginManager}, {@code false} otherwise */ protected boolean isAvailable ( @NotNull final PluginDependency dependency ) { boolean dependencyLoaded = false; // Checking plugins available in this manager for ( final P available : availablePlugins ) { if ( dependency.accept ( available.getInformation () ) ) { dependencyLoaded = true; break; } } // Checking plugins available in parent managers synchronized ( parentManagers ) { for ( final PluginManager parentManager : parentManagers ) { if ( parentManager.isAvailable ( dependency ) ) { dependencyLoaded = true; break; } } } return dependencyLoaded; } /** * Returns whether or not {@link PluginDependency} is available in {@link #recentlyDetected} list. * * @param dependency {@link PluginDependency} to look for * @return {@code true} if {@link PluginDependency} is available in {@link #recentlyDetected} list, {@code false} otherwise */ protected boolean isDetected ( @NotNull final PluginDependency dependency ) { boolean dependencyDetected = false; for ( final DetectedPlugin

detected : recentlyDetected ) { if ( dependency.accept ( detected.getInformation () ) ) { dependencyDetected = true; break; } } return dependencyDetected; } /** * Returns {@link DetectedPlugin} for the specified {@link File} if available, {@code null} otherwise. * * @param file {@link File} to find {@link DetectedPlugin} for * @return {@link DetectedPlugin} for the specified {@link File} if available, {@code null} otherwise */ @Nullable public DetectedPlugin

getDetectedPlugin ( @Nullable final File file ) { final DetectedPlugin

detectedPlugin; synchronized ( checkLock ) { if ( file != null ) { final String path = FileUtils.canonicalPath ( file ); if ( detectedPluginsByPath.containsKey ( path ) ) { // Cached plugin information detectedPlugin = detectedPluginsByPath.get ( path ); } else { // Loading plugin information detectedPlugin = detectPlugin ( file ); } } else { detectedPlugin = null; } } return detectedPlugin; } /** * Returns {@link DetectedPlugin} for the specified {@link File} if available, {@code null} otherwise. * Returns null in case plugin file cannot be read or if it is incorrect. * * @param file plugin file to process * @return {@link DetectedPlugin} for the specified {@link File} if available, {@code null} otherwise */ @Nullable protected DetectedPlugin

detectPlugin ( @NotNull final File file ) { DetectedPlugin

detectedPlugin = null; try { final String pluginDescriptor = getPluginDescriptorFile (); final String pluginLogo = getPluginLogoFile (); final ZipFile zipFile = new ZipFile ( file ); final Enumeration entries = zipFile.entries (); while ( entries.hasMoreElements () ) { final ZipEntry entry = ( ZipEntry ) entries.nextElement (); if ( entry.getName ().endsWith ( pluginDescriptor ) ) { // Reading plugin information final InputStream inputStream = zipFile.getInputStream ( entry ); final PluginInformation info = XmlUtils.fromXML ( inputStream ); inputStream.close (); // Reading plugin icon final Icon logo; if ( !SystemUtils.isHeadlessEnvironment () ) { final ZipEntry logoEntry = new ZipEntry ( ZipUtils.getFileLocation ( entry ) + pluginLogo ); final InputStream logoInputStream = zipFile.getInputStream ( logoEntry ); if ( logoInputStream != null ) { // todo This will force logo to always be static logo = ImageUtils.toImageIcon ( ImageUtils.loadBufferedImage ( logoInputStream ) ); logoInputStream.close (); } else { logo = null; } } else { logo = null; } // Checking whether we have already detected this plugin or not if ( !wasDetected ( file.getParent (), file.getName () ) ) { // Cache and return new plugin information // This cache map is filled here since it has different usage cases final DetectedPlugin

plugin = new DetectedPlugin

( file.getParent (), file.getName (), info, logo ); detectedPluginsByPath.put ( FileUtils.canonicalPath ( file ), plugin ); detectedPlugin = plugin; break; } break; } } zipFile.close (); } catch ( final IOException e ) { LoggerFactory.getLogger ( PluginManager.class ).error ( "Unable to read plugin information", e ); } return detectedPlugin; } /** * Returns whether or not specified plugin file was already detected before. * * @param pluginFolder plugin directory * @param pluginFile plugin file * @return {@code true} if specified plugin file was already detected before, {@code false} otherwise */ protected boolean wasDetected ( @Nullable final String pluginFolder, @Nullable final String pluginFile ) { boolean detected = false; for ( final DetectedPlugin

plugin : detectedPlugins ) { if ( Objects.equals ( plugin.getPluginFolder (), pluginFolder ) && Objects.equals ( plugin.getPluginFileName (), pluginFile ) ) { detected = true; break; } } return detected; } /** * Initializes earlier detected plugins. * Also informs listeners about appropriate events. */ protected void initializeDetectedPlugins () { if ( !recentlyDetected.isEmpty () ) { LoggerFactory.getLogger ( PluginManager.class ).info ( "Initializing plugins" ); // Informing about newly detected plugins firePluginsDetected ( recentlyDetected ); // Initializing plugins initializeDetectedPluginsImpl (); // Sorting plugins according to their initialization strategies applyInitializationStrategy (); // Properly sorting recently initialized plugins Collections.sort ( recentlyInitialized, new Comparator

() { @Override public int compare ( final P o1, final P o2 ) { final Integer i1 = availablePlugins.indexOf ( o1 ); final Integer i2 = availablePlugins.indexOf ( o2 ); return i1.compareTo ( i2 ); } } ); LoggerFactory.getLogger ( PluginManager.class ).info ( "Plugins initialization finished" ); // Informing about new plugins initialization firePluginsInitialized ( recentlyInitialized ); } else { LoggerFactory.getLogger ( PluginManager.class ).info ( "No new plugins found" ); } } /** * Initializes earlier detected plugins. */ protected void initializeDetectedPluginsImpl () { // Map to store plugin libraries final Map> pluginLibraries = new HashMap> (); // Clearing recently initialized plugins list // This is required to properly inform about newly loaded plugins later recentlyInitialized.clear (); // Adding recently detected into the end of the detected plugins list detectedPlugins.addAll ( recentlyDetected ); // Initializing detected plugins final String acceptedPluginType = getAcceptedPluginType (); for ( final DetectedPlugin

detectedPlugin : detectedPlugins ) { // Skip plugins we have already tried to initialize if ( detectedPlugin.getStatus () != PluginStatus.detected ) { continue; } // Check that plugin location is available, meaning that it's not a preloaded plugin final File pluginFile = detectedPlugin.getFile (); final String pluginsDirectoryPath = getPluginsDirectoryPath (); if ( pluginFile == null || pluginsDirectoryPath == null ) { throw new PluginException ( String.format ( "Preloaded plugin should be properly registered separately: %s", detectedPlugin.getInformation ().toString () ) ); } // Construct log prefix final PluginInformation info = detectedPlugin.getInformation (); final String logPrefix = String.format ( "[%s] [%s] ", FileUtils.getRelativePath ( pluginFile, new File ( pluginsDirectoryPath ) ), info ); try { // Starting to load plugin now LoggerFactory.getLogger ( PluginManager.class ).info ( logPrefix + "Initializing plugin" ); detectedPlugin.setStatus ( PluginStatus.loading ); // Checking plugin type as we don't want (for example) to load server plugins on client side if ( acceptedPluginType != null && ( info.getType () == null || !info.getType ().equals ( acceptedPluginType ) ) ) { pluginLoadFailed ( detectedPlugin, "Wrong type", null, logPrefix, String.format ( "Plugin of type '%s' cannot be loaded, required type is: %s", info.getType (), acceptedPluginType ) ); continue; } // Checking that this is latest plugin version of all available // Usually there shouldn't be different versions of the same plugin but everyone make mistakes if ( isDeprecatedVersion ( detectedPlugin ) ) { pluginLoadFailed ( detectedPlugin, "Deprecated", null, logPrefix, "Plugin is deprecated, newer version loaded instead" ); continue; } // Checking that this plugin version is not yet loaded // This might occur in case the same plugin appears more than once in different files if ( isSameVersionAlreadyLoaded ( detectedPlugin, detectedPlugins ) ) { pluginLoadFailed ( detectedPlugin, "Duplicate", null, logPrefix, "Plugin is a duplicate, it will be loaded from another source" ); continue; } // Checking that plugin filter accepts this plugin final Filter> pluginFilter = getPluginFilter (); if ( pluginFilter != null && !pluginFilter.accept ( detectedPlugin ) ) { pluginLoadFailed ( detectedPlugin, "Filtered", null, logPrefix, "Plugin was not accepted by plugin filter" ); continue; } // Checking plugin dependencies final List dependencies = detectedPlugin.getInformation ().getDependencies (); if ( dependencies != null ) { for ( final PluginDependency dependency : dependencies ) { // Checking whether or not dependency is mandatory and whether or not it is available final String dependencyId = dependency.getPluginId (); if ( !dependency.isOptional () && !isPluginAvailable ( dependencyId ) ) { // If it is mandatory and not available - check related managers for that dependency boolean available = false; for ( final PluginManager relatedManager : parentManagers ) { if ( relatedManager.isPluginAvailable ( dependencyId ) ) { available = true; break; } } if ( !available ) { pluginLoadFailed ( detectedPlugin, "Incomplete", null, logPrefix, String.format ( "Mandatory plugin dependency is missing: %s", dependencyId ) ); break; } } } if ( detectedPlugin.getStatus () == PluginStatus.failed ) { continue; } } // Collecting plugin and its libraries JAR paths final List pluginClassPath = new ArrayList ( 1 + info.getLibrariesCount () ); pluginClassPath.add ( pluginFile.toURI ().toURL () ); if ( info.getLibraries () != null ) { for ( final PluginLibrary library : info.getLibraries () ) { final File file = new File ( detectedPlugin.getPluginFolder (), library.getFile () ); if ( file.exists () ) { // Adding library URI to path pluginClassPath.add ( file.toURI ().toURL () ); // Saving library information for further checks Map libraries = pluginLibraries.get ( library.getId () ); if ( libraries == null ) { libraries = new HashMap ( 1 ); pluginLibraries.put ( library.getId (), libraries ); } libraries.put ( library, info ); } else { pluginLoadFailed ( detectedPlugin, "Incomplete", null, logPrefix, String.format ( "Plugin library was not found: %s", file.getAbsolutePath () ) ); break; } } if ( detectedPlugin.getStatus () == PluginStatus.failed ) { continue; } } try { // Configuring class loader for our plugin final ClassLoader classLoader = configureClassLoaderForPlugin ( detectedPlugin, pluginClassPath ); // Loading plugin class final Class pluginClass = classLoader.loadClass ( info.getMainClass () ); final P plugin = ReflectUtils.createInstance ( pluginClass ); plugin.setPluginManager ( PluginManager.this ); plugin.setDetectedPlugin ( detectedPlugin ); // Saving initialized plugin availablePlugins.add ( plugin ); availablePluginsById.put ( plugin.getId (), plugin ); availablePluginsByClass.put ( plugin.getClass (), plugin ); recentlyInitialized.add ( plugin ); // Updating detected plugin status LoggerFactory.getLogger ( PluginManager.class ).info ( logPrefix + "Plugin initialized" ); detectedPlugin.setStatus ( PluginStatus.loaded ); detectedPlugin.setPlugin ( plugin ); } catch ( final Exception e ) { // Something happened while performing plugin class load pluginLoadFailed ( detectedPlugin, "Plugin initialization exception", e, logPrefix, "Unable to initialize plugin" ); } } catch ( final Exception e ) { // Something happened while checking plugin information pluginLoadFailed ( detectedPlugin, "Plugin data exception", e, logPrefix, "Unable to initialize plugin data" ); } } // Checking for same/similar libraries used within plugins // todo There should be a flag for libraries to specify when duplicates usage is intended boolean sameLibrariesInPlugins = false; for ( final Map.Entry> libraries : pluginLibraries.entrySet () ) { final Map sameLibraries = libraries.getValue (); if ( sameLibraries.size () > 1 ) { final String title = sameLibraries.keySet ().iterator ().next ().getTitle (); final StringBuilder sb = new StringBuilder ( "Library [ " ).append ( title ).append ( " ] was found in plugins: " ); for ( final Map.Entry library : sameLibraries.entrySet () ) { final PluginInformation plugin = library.getValue (); final String libraryVersion = library.getKey ().getVersion (); sb.append ( "[ " ).append ( plugin.toString () ).append ( ", version " ).append ( libraryVersion ).append ( " ] " ); } LoggerFactory.getLogger ( PluginManager.class ).info ( sb.toString () ); sameLibrariesInPlugins = true; break; } } if ( sameLibrariesInPlugins ) { final String msg = "Make sure that the same library usage within different plugins was actually your intent"; LoggerFactory.getLogger ( PluginManager.class ).info ( msg ); } } /** * Logs {@link Plugin} loading error and updates {@link DetectedPlugin} information. * * @param detectedPlugin {@link DetectedPlugin} * @param failureCause {@link Plugin} initialization failure cause * @param exception {@link Throwable} that occurred during {@link Plugin} initialization * @param logPrefix prefix for logged messages only * @param message message to log and save in {@link DetectedPlugin} */ private void pluginLoadFailed ( @NotNull final DetectedPlugin

detectedPlugin, @NotNull final String failureCause, @Nullable final Exception exception, @NotNull final String logPrefix, @NotNull final String message ) { LoggerFactory.getLogger ( PluginManager.class ).error ( logPrefix + message ); detectedPlugin.setStatus ( PluginStatus.failed ); detectedPlugin.setFailureCause ( failureCause ); detectedPlugin.setException ( exception ); detectedPlugin.setExceptionMessage ( message ); } /** * Returns {@link ClassLoader} configured for the specified {@link DetectedPlugin}. * * @param detectedPlugin {@link DetectedPlugin} * @param pluginClassPath {@link List} of plugin class path {@link URL}s * @return {@link ClassLoader} configured for the specified {@link DetectedPlugin} */ @NotNull protected ClassLoader configureClassLoaderForPlugin ( @NotNull final DetectedPlugin

detectedPlugin, @NotNull final List pluginClassPath ) { // Choosing class loader based on configured type ClassLoader classLoader; switch ( getClassLoaderType () ) { case system: { classLoader = ClassLoader.getSystemClassLoader (); break; } case context: default: { final ClassLoader ccl = Thread.currentThread ().getContextClassLoader (); classLoader = ccl != null ? ccl : getClass ().getClassLoader (); break; } case global: { classLoader = getGlobalClassLoader (); break; } case local: { classLoader = getLocalClassLoader (); break; } case separate: { classLoader = createPluginClassLoader ( new URL[ 0 ] ); break; } } try { // Adding plugin classpath into class loader via utility ReflectUtils.addToClasspath ( classLoader, pluginClassPath ); } catch ( final Exception e ) { // We might want to handle exception differently if fallback mechanism is enabled if ( isProvideClassLoaderFallback () ) { // Using plugin class loader with a custom class path classLoader = new PluginClassLoader ( pluginClassPath.toArray ( new URL[ pluginClassPath.size () ] ), classLoader ); } else { throw new PluginException ( String.format ( "Unable to load detected plugin: %s", detectedPlugin.getInformation ().toString () ), e ); } } return classLoader; } /** * Returns whether the list of detected plugins contain a newer version of the specified plugin or not. * * @param plugin plugin to compare with other detected plugins * @return true if the list of detected plugins contain a newer version of the specified plugin, false otherwise */ public boolean isDeprecatedVersion ( @NotNull final DetectedPlugin

plugin ) { synchronized ( checkLock ) { return isDeprecatedVersion ( plugin, detectedPlugins ); } } /** * Returns whether or not the {@link List} contains a newer version of the specified {@link DetectedPlugin}. * * @param plugin {@link DetectedPlugin} to compare others from {@link List} with * @param detectedPlugins {@link List} of {@link DetectedPlugin} to look for newer version in * @return {@code true} if the {@link List} contains a newer version of the specified {@link DetectedPlugin}, {@code false} otherwise */ public boolean isDeprecatedVersion ( @NotNull final DetectedPlugin

plugin, @NotNull final List> detectedPlugins ) { boolean isDeprecatedVersion = false; final PluginInformation pluginInfo = plugin.getInformation (); for ( final DetectedPlugin detectedPlugin : detectedPlugins ) { if ( detectedPlugin != plugin ) { final PluginInformation detectedPluginInfo = detectedPlugin.getInformation (); if ( detectedPluginInfo.getId ().equals ( pluginInfo.getId () ) && detectedPluginInfo.getVersion () != null && pluginInfo.getVersion () != null && detectedPluginInfo.getVersion ().isNewerThan ( pluginInfo.getVersion () ) ) { isDeprecatedVersion = true; break; } } } return isDeprecatedVersion; } /** * Returns whether or not the {@link List} contains the same version of the specified {@link DetectedPlugin}. * * @param plugin {@link DetectedPlugin} to compare others from {@link List} with * @param detectedPlugins {@link List} of {@link DetectedPlugin} to look for the same version in * @return {@code true} if the {@link List} contains the same version of the specified {@link DetectedPlugin}, {@code false} otherwise */ protected boolean isSameVersionAlreadyLoaded ( @NotNull final DetectedPlugin

plugin, @NotNull final List> detectedPlugins ) { boolean isSameVersionAlreadyLoaded = false; final PluginInformation pluginInfo = plugin.getInformation (); for ( final DetectedPlugin detectedPlugin : detectedPlugins ) { if ( detectedPlugin != plugin ) { final PluginInformation detectedPluginInfo = detectedPlugin.getInformation (); if ( detectedPluginInfo.getId ().equals ( pluginInfo.getId () ) && ( detectedPluginInfo.getVersion () == null && pluginInfo.getVersion () == null || detectedPluginInfo.getVersion ().isSame ( pluginInfo.getVersion () ) ) && detectedPlugin.getStatus () == PluginStatus.loaded ) { isSameVersionAlreadyLoaded = true; break; } } } return isSameVersionAlreadyLoaded; } /** * Sorting plugins according to their initialization strategies. * todo Take plugin dependencies into account with top priority here */ protected void applyInitializationStrategy () { if ( availablePlugins.size () > 1 ) { // Splitting plugins by initial groups final List

beforeAll = new ArrayList

( availablePlugins.size () ); final List

middle = new ArrayList

( availablePlugins.size () ); final List

afterAll = new ArrayList

( availablePlugins.size () ); for ( final P plugin : availablePlugins ) { final InitializationStrategy strategy = plugin.getInitializationStrategy (); if ( strategy.getPluginId ().equals ( InitializationStrategy.ALL_ID ) ) { switch ( strategy.getType () ) { case before: { beforeAll.add ( plugin ); break; } case any: { middle.add ( plugin ); break; } case after: { afterAll.add ( plugin ); break; } } } else { middle.add ( plugin ); } } // Sorting plugins in appropriate order // This order is not used by PluginManager itself due to possible unstructured plugin loading if ( middle.size () == 0 ) { // Combining all plugins into single list availablePlugins.clear (); availablePlugins.addAll ( beforeAll ); availablePlugins.addAll ( afterAll ); } else { // Sorting middle plugins properly final List

sortedMiddle = new ArrayList

( middle ); for ( final P plugin : middle ) { final InitializationStrategy strategy = plugin.getInitializationStrategy (); final String id = strategy.getPluginId (); if ( !plugin.getId ().equals ( id ) ) { final int oldIndex = sortedMiddle.indexOf ( plugin ); for ( int index = 0; index < sortedMiddle.size (); index++ ) { if ( sortedMiddle.get ( index ).getId ().equals ( id ) ) { switch ( strategy.getType () ) { case before: { sortedMiddle.remove ( oldIndex ); if ( oldIndex < index ) { sortedMiddle.add ( index - 1, plugin ); } else { sortedMiddle.add ( index, plugin ); } break; } case after: { sortedMiddle.remove ( oldIndex ); if ( oldIndex < index ) { sortedMiddle.add ( index, plugin ); } else { sortedMiddle.add ( index + 1, plugin ); } break; } } break; } } } } // Combining all plugins into single list availablePlugins.clear (); availablePlugins.addAll ( beforeAll ); availablePlugins.addAll ( sortedMiddle ); availablePlugins.addAll ( afterAll ); } } } /** * Returns list of detected plugins. * * @return list of detected plugins */ public List> getDetectedPlugins () { synchronized ( checkLock ) { return new ImmutableList> ( detectedPlugins ); } } /** * Returns list of available loaded plugins. * * @return list of available loaded plugins */ public List

getAvailablePlugins () { synchronized ( checkLock ) { return new ImmutableList

( availablePlugins ); } } /** * Returns whether or not instance of the {@link Plugin} with the specified identifier is available. * * @param pluginId {@link Plugin} identifier * @return {@code true} if instance of the {@link Plugin} with the specified identifier is available, {@code false} otherwise */ public boolean isPluginAvailable ( @NotNull final String pluginId ) { return getPlugin ( pluginId ) != null; } /** * Returns {@link Plugin} instance by its identifier or {@code null} if it's not available. * * @param pluginId {@link Plugin} identifier * @param {@link Plugin} type * @return {@link Plugin} instance by its identifier or {@code null} if it's not available */ @Nullable public T getPlugin ( @NotNull final String pluginId ) { synchronized ( checkLock ) { return ( T ) availablePluginsById.get ( pluginId ); } } /** * Returns {@link Plugin} instance by its {@link Class} or {@code null} if it's not available. * Returns available plugin instance by its class. * * @param pluginClass {@link Plugin} {@link Class} * @param {@link Plugin} type * @return {@link Plugin} instance by its {@link Class} or {@code null} if it's not available */ @Nullable public T getPlugin ( @NotNull final Class pluginClass ) { synchronized ( checkLock ) { return ( T ) availablePluginsByClass.get ( pluginClass ); } } /** * Returns amount of detected plugins. * * @return amount of detected loaded plugins */ public int getDetectedPluginsAmount () { synchronized ( checkLock ) { return detectedPlugins.size (); } } /** * Returns amount of successfully loaded plugins. * * @return amount of successfully loaded plugins */ public int getLoadedPluginsAmount () { synchronized ( checkLock ) { return availablePlugins.size (); } } /** * Returns amount of plugins which have failed to load. * There might be a lot of reasons why they failed to load - exception, broken JAR, missing libraries etc. * Simply check the log or retrieve failure cause from DetectedPlugin to understand what happened. * * @return amount of plugins which have failed to load */ public int getFailedPluginsAmount () { synchronized ( checkLock ) { return detectedPlugins.size () - availablePlugins.size (); } } /** * Registers specified {@link PluginsRunnable} for {@link PluginsListener#pluginsCheckStarted(String, boolean)} event. * * @param runnable {@link PluginsRunnable} to register for event * @return created {@link PluginsListener} */ @NotNull public PluginsListener

onPluginsScanStart ( @NotNull final DirectoryRunnable runnable ) { final PluginsAdapter

listener = new PluginsAdapter

() { @Override public void pluginsCheckStarted ( @NotNull final String directory, final boolean recursive ) { runnable.run ( directory, recursive ); } }; addPluginsListener ( listener ); return listener; } /** * Registers specified {@link PluginsRunnable} for {@link PluginsListener#pluginsCheckEnded(String, boolean)} event. * * @param runnable {@link PluginsRunnable} to register for event * @return created {@link PluginsListener} */ @NotNull public PluginsListener

onPluginsScanEnd ( @NotNull final DirectoryRunnable runnable ) { final PluginsAdapter

listener = new PluginsAdapter

() { @Override public void pluginsCheckEnded ( @NotNull final String directory, final boolean recursive ) { runnable.run ( directory, recursive ); } }; addPluginsListener ( listener ); return listener; } /** * Registers specified {@link PluginsRunnable} for {@link PluginsListener#pluginsDetected(List)} event. * * @param runnable {@link PluginsRunnable} to register for event * @return created {@link PluginsListener} */ @NotNull public PluginsListener

onPluginsDetection ( @NotNull final DetectedPluginsRunnable

runnable ) { final PluginsAdapter

listener = new PluginsAdapter

() { @Override public void pluginsDetected ( @NotNull final List> detectedPlugins ) { runnable.run ( detectedPlugins ); } }; addPluginsListener ( listener ); return listener; } /** * Registers specified {@link PluginsRunnable} for {@link PluginsListener#pluginsInitialized(List)} event. * * @param runnable {@link PluginsRunnable} to register for event * @return created {@link PluginsListener} */ @NotNull public PluginsListener

onPluginsInitialization ( @NotNull final PluginsRunnable

runnable ) { final PluginsAdapter

listener = new PluginsAdapter

() { @Override public void pluginsInitialized ( @NotNull final List

plugins ) { runnable.run ( plugins ); } }; addPluginsListener ( listener ); return listener; } /** * Adds {@link PluginsListener}. * * @param listener {@link PluginsListener} to add */ public void addPluginsListener ( @NotNull final PluginsListener

listener ) { synchronized ( listeners ) { listeners.add ( listener ); } } /** * Removes {@link PluginsListener}. * * @param listener {@link PluginsListener} to remove */ public void removePluginsListener ( @NotNull final PluginsListener

listener ) { synchronized ( listeners ) { listeners.remove ( listener ); } } /** * Informs all added {@link PluginsListener}s about plugins check operation start. * * @param directory checked plugins directory path * @param recursive whether plugins directory subfolders are checked recursively or not */ public void firePluginsCheckStarted ( @NotNull final String directory, final boolean recursive ) { synchronized ( listeners ) { for ( final PluginsListener

listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsCheckStarted ( directory, recursive ); } } } /** * Informs all added {@link PluginsListener}s about plugins check operation end. * * @param directory checked plugins directory path * @param recursive whether plugins directory subfolders are checked recursively or not */ public void firePluginsCheckEnded ( @NotNull final String directory, final boolean recursive ) { synchronized ( listeners ) { for ( final PluginsListener

listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsCheckEnded ( directory, recursive ); } } } /** * Informs all added {@link PluginsListener}s about newly detected plugins. * * @param plugins newly detected plugins list */ public void firePluginsDetected ( @NotNull final List> plugins ) { synchronized ( listeners ) { final ImmutableList> immutable = new ImmutableList> ( plugins ); for ( final PluginsListener

listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsDetected ( immutable ); } } } /** * Informs all added {@link PluginsListener}s about newly initialized plugins. * * @param plugins newly initialized plugins list */ public void firePluginsInitialized ( @NotNull final List

plugins ) { synchronized ( listeners ) { final ImmutableList

immutable = new ImmutableList

( plugins ); for ( final PluginsListener

listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsInitialized ( immutable ); } } } /** * Returns local {@link PluginClassLoader} for this specific {@link PluginManager}. * * @return local {@link PluginClassLoader} for this specific {@link PluginManager} */ @NotNull protected PluginClassLoader getLocalClassLoader () { if ( localClassLoader == null ) { synchronized ( this ) { if ( localClassLoader == null ) { localClassLoader = createPluginClassLoader ( new URL[ 0 ] ); } } } return localClassLoader; } /** * Returns global {@link PluginClassLoader} shared between all {@link PluginManager}s. * * @return global {@link PluginClassLoader} shared between all {@link PluginManager}s */ @NotNull protected PluginClassLoader getGlobalClassLoader () { if ( globalClassLoader == null ) { synchronized ( PluginManager.class ) { if ( globalClassLoader == null ) { globalClassLoader = createPluginClassLoader ( new URL[ 0 ] ); } } } return globalClassLoader; } /** * Returns new {@link PluginClassLoader} for this specific {@link PluginManager}. * * @param classpath class loader class path * @return new {@link PluginClassLoader} for this specific {@link PluginManager} */ @NotNull protected PluginClassLoader createPluginClassLoader ( @NotNull final URL[] classpath ) { return new PluginClassLoader ( classpath, PluginManager.class.getClassLoader () ); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy