diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java index 00fa226445..0b7ea98512 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java @@ -30,7 +30,7 @@ import ghidra.framework.model.Project; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.plugintool.util.PluginClassManager; +import ghidra.framework.plugintool.util.PluginsConfiguration; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -413,8 +413,8 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat } @Override - public PluginClassManager getPluginClassManager() { - return null; + public PluginsConfiguration createPluginsConfigurations() { + return null; // prevent slow class loading } @Override diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPluginsConfiguration.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPluginsConfiguration.java new file mode 100644 index 0000000000..0fcac13073 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPluginsConfiguration.java @@ -0,0 +1,29 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.framework.main; + +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.util.PluginsConfiguration; + +/** + * A configuration that only includes {@link ApplicationLevelPlugin} plugins. + */ +class ApplicationLevelPluginsConfiguration extends PluginsConfiguration { + @Override + protected boolean accepts(Class c) { + return ApplicationLevelPlugin.class.isAssignableFrom(c); + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java index 55bbdbe917..757aebcde2 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java @@ -131,7 +131,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { private WindowListener windowListener; private DockingAction configureToolAction; - private PluginClassManager pluginClassManager; /** * Construct a new Ghidra Project Window. @@ -190,11 +189,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { } @Override - public PluginClassManager getPluginClassManager() { - if (pluginClassManager == null) { - pluginClassManager = new PluginClassManager(ApplicationLevelPlugin.class, null); - } - return pluginClassManager; + protected PluginsConfiguration createPluginsConfigurations() { + return new ApplicationLevelPluginsConfiguration(); } public void selectFiles(Set files) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProgramaticUseOnly.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProgramaticUseOnly.java index e27772fc84..b930b45ebc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProgramaticUseOnly.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProgramaticUseOnly.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +16,9 @@ package ghidra.framework.main; /** - * Marker interface for plugins that only get constructed programatically for specific purposes. + * Marker interface for plugins that only get constructed programmatically for specific purposes. * Plugins that implement this interface should never be added via the config GUIs. */ public interface ProgramaticUseOnly { - + // marker interface } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/DeafultPluginPackagingProvider.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/DeafultPluginPackagingProvider.java index 05ba901de1..b6fe7bdfcd 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/DeafultPluginPackagingProvider.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/DeafultPluginPackagingProvider.java @@ -20,13 +20,13 @@ import java.util.List; import ghidra.framework.plugintool.util.*; /** - * The default plugin package provider that uses the {@link PluginClassManager} to supply packages + * The default plugin package provider that uses the {@link PluginsConfiguration} to supply packages */ public class DeafultPluginPackagingProvider implements PluginPackagingProvider { - private PluginClassManager pluginClassManager; + private PluginsConfiguration pluginClassManager; - DeafultPluginPackagingProvider(PluginClassManager pluginClassManager) { + DeafultPluginPackagingProvider(PluginsConfiguration pluginClassManager) { this.pluginClassManager = pluginClassManager; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ModalPluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ModalPluginTool.java index 80187e7158..1d7c790273 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ModalPluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ModalPluginTool.java @@ -17,13 +17,13 @@ package ghidra.framework.plugintool; import ghidra.framework.main.AppInfo; import ghidra.framework.model.Project; -import ghidra.framework.plugintool.util.PluginClassManager; +import ghidra.framework.plugintool.util.PluginsConfiguration; /** * PluginTool that is used by the Merge process to resolve conflicts * when a file is being checked into a server repository. This tool * is modal while it is visible. - * + * */ public class ModalPluginTool extends PluginTool { @@ -44,7 +44,7 @@ public class ModalPluginTool extends PluginTool { } @Override - public PluginClassManager getPluginClassManager() { - return null; + public PluginsConfiguration createPluginsConfigurations() { + return null; // no need to load plugins } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginConfigurationModel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginConfigurationModel.java index 5dc3c283f0..23f2e9cb44 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginConfigurationModel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginConfigurationModel.java @@ -34,7 +34,7 @@ public class PluginConfigurationModel { public PluginConfigurationModel(PluginTool tool) { this(new DefaultPluginInstaller(tool), - new DeafultPluginPackagingProvider(tool.getPluginClassManager())); + new DeafultPluginPackagingProvider(tool.getPluginsConfiguration())); } public PluginConfigurationModel(PluginInstaller pluginInstaller, diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java index 55cb8ecf17..e8ffc738f9 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom.Element; +import ghidra.framework.main.UtilityPluginPackage; import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainObject; import ghidra.framework.options.SaveState; @@ -33,15 +34,40 @@ import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.MultipleCauses; class PluginManager { - static final Logger log = LogManager.getLogger(PluginManager.class); + private static final Logger log = LogManager.getLogger(PluginManager.class); + private PluginsConfiguration pluginsConfiguration; private List pluginList = new ArrayList<>(); private PluginTool tool; private ServiceManager serviceMgr; - PluginManager(PluginTool tool, ServiceManager serviceMgr) { + PluginManager(PluginTool tool, ServiceManager serviceMgr, + PluginsConfiguration pluginsConfiguration) { this.tool = tool; this.serviceMgr = serviceMgr; + this.pluginsConfiguration = pluginsConfiguration; + } + + void installUtilityPlugins() throws PluginException { + + PluginPackage utilityPackage = PluginPackage.getPluginPackage(UtilityPluginPackage.NAME); + List descriptions = + pluginsConfiguration.getPluginDescriptions(utilityPackage); + if (descriptions == null) { + return; + } + + Set classNames = new HashSet<>(); + for (PluginDescription description : descriptions) { + String pluginClass = description.getPluginClass().getName(); + classNames.add(pluginClass); + } + + addPlugins(classNames); + } + + PluginsConfiguration getPluginsConfiguration() { + return pluginsConfiguration; } boolean acceptData(DomainFile[] data) { @@ -55,9 +81,9 @@ class PluginManager { } /** - * Identify plugin which will accept specified URL. If no plugin accepts URL it will be - * rejected and false returned. If a plugin can accept the specified URL it will attempt to - * process and return true if successful. + * Identify plugin which will accept specified URL. If no plugin accepts URL it will be + * rejected and false returned. If a plugin can accept the specified URL it will attempt to + * process and return true if successful. * The user may be prompted if connecting to the URL requires user authentication. * @param url read-only resource URL * @return true if URL accepted and processed else false @@ -72,7 +98,7 @@ class PluginManager { return false; } - public void dispose() { + void dispose() { for (Iterator it = pluginList.iterator(); it.hasNext();) { Plugin plugin = it.next(); plugin.cleanup(); @@ -255,15 +281,14 @@ class PluginManager { } void saveToXml(Element root, boolean includeConfigState) { - PluginClassManager pluginClassManager = tool.getPluginClassManager(); - pluginClassManager.addXmlElementsForPlugins(root, pluginList); + + pluginsConfiguration.savePluginsToXml(root, pluginList); if (!includeConfigState) { return; } SaveState saveState = new SaveState("PLUGIN_STATE"); - Iterator it = pluginList.iterator(); while (it.hasNext()) { Plugin p = it.next(); @@ -279,8 +304,8 @@ class PluginManager { void restorePluginsFromXml(Element root) throws PluginException { boolean isOld = isOldToolConfig(root); - Collection classNames = - isOld ? getPluginClassNamesFromOldXml(root) : getPluginClassNamesToLoad(root); + Collection classNames = isOld ? getPluginClassNamesFromOldXml(root) + : pluginsConfiguration.getPluginClassNames(root); Map map = isOld ? getPluginSavedStates(root, "PLUGIN") : getPluginSavedStates(root, "PLUGIN_STATE"); @@ -324,19 +349,14 @@ class PluginManager { String className = elem.getAttributeValue("CLASS"); classNames.add(className); } - PluginClassManager pluginClassManager = tool.getPluginClassManager(); - return pluginClassManager.fillInPackageClasses(classNames); + + return pluginsConfiguration.getPluginNamesByCurrentPackage(classNames); } private boolean isOldToolConfig(Element root) { return root.getChild("PLUGIN") != null; } - private Set getPluginClassNamesToLoad(Element root) { - PluginClassManager pluginClassManager = tool.getPluginClassManager(); - return pluginClassManager.getPluginClasses(root); - } - /** * Restore the data state from the given XML element. * @param root XML element containing plugins' data state @@ -465,8 +485,9 @@ class PluginManager { // TODO: this following loop is searching for any non-loaded Plugin that implements // the required service class interface. // This doesn't seem exactly right as a Service implementation shouldn't - // be automagically pulled in and instantiated UNLESS it was specified as the "defaultProvider", - // which we've already checked for in the previous PluginUtils.getDefaultProviderForServiceClass(). + // be automagically pulled in and instantiated UNLESS it was specified as the + // "defaultProvider", which we've already checked for in the previous + // PluginUtils.getDefaultProviderForServiceClass(). // TODO: this also should be filtered by the PluginClassManager so we don't // pull in classes that have been excluded. for (Class pc : ClassSearcher.getClasses(Plugin.class)) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 932a4dfaf9..6d80ce3c62 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -45,7 +45,8 @@ import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.cmd.Command; -import ghidra.framework.main.*; +import ghidra.framework.main.AppInfo; +import ghidra.framework.main.UserAgreementDialog; import ghidra.framework.model.*; import ghidra.framework.options.*; import ghidra.framework.plugintool.dialog.ExtensionTableProvider; @@ -175,7 +176,7 @@ public abstract class PluginTool extends AbstractDockingTool { eventMgr = new EventManager(this); serviceMgr = new ServiceManager(); installServices(); - pluginMgr = new PluginManager(this, serviceMgr); + pluginMgr = new PluginManager(this, serviceMgr, createPluginsConfigurations()); dialogMgr = new DialogManager(this); initActions(); initOptions(); @@ -192,7 +193,13 @@ public abstract class PluginTool extends AbstractDockingTool { // non-public constructor for stub subclasses } - public abstract PluginClassManager getPluginClassManager(); + protected PluginsConfiguration createPluginsConfigurations() { + return new DefaultPluginsConfiguration(); + } + + protected PluginsConfiguration getPluginsConfiguration() { + return pluginMgr.getPluginsConfiguration(); + } /** * This method exists here, as opposed to inline in the constructor, so that subclasses can @@ -233,21 +240,15 @@ public abstract class PluginTool extends AbstractDockingTool { */ protected void installUtilityPlugins() { - PluginClassManager classManager = getPluginClassManager(); - PluginPackage utilityPackage = PluginPackage.getPluginPackage(UtilityPluginPackage.NAME); - List descriptions = classManager.getPluginDescriptions(utilityPackage); - - Set classNames = new HashSet<>(); - if (descriptions == null) { - return; - } - for (PluginDescription description : descriptions) { - String pluginClass = description.getPluginClass().getName(); - classNames.add(pluginClass); - } - try { - addPlugins(classNames); + checkedRunSwingNow(() -> { + try { + pluginMgr.installUtilityPlugins(); + } + finally { + setConfigChanged(true); + } + }, PluginException.class); } catch (PluginException e) { Msg.showError(this, null, "Error Adding Utility Plugins", diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAlonePluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAlonePluginTool.java index 45c19b2746..9740c4aada 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAlonePluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAlonePluginTool.java @@ -23,12 +23,12 @@ import docking.action.*; import docking.tool.ToolConstants; import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; -import ghidra.framework.plugintool.util.PluginClassManager; +import ghidra.framework.plugintool.util.PluginsConfiguration; import ghidra.util.HelpLocation; public class StandAlonePluginTool extends PluginTool { - private PluginClassManager pluginClassManager; + private PluginsConfiguration pluginClassManager; private DockingAction configureToolAction; private final GenericStandAloneApplication app; private final String name; @@ -39,14 +39,6 @@ public class StandAlonePluginTool extends PluginTool { this.name = name; } - @Override - public PluginClassManager getPluginClassManager() { - if (pluginClassManager == null) { - pluginClassManager = new PluginClassManager(Plugin.class, null); - } - return pluginClassManager; - } - @Override public void addExitAction() { DockingAction exitAction = new DockingAction("Exit", ToolConstants.TOOL_OWNER) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/DefaultPluginsConfiguration.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/DefaultPluginsConfiguration.java new file mode 100644 index 0000000000..33f161605d --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/DefaultPluginsConfiguration.java @@ -0,0 +1,28 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.framework.plugintool.util; + +import ghidra.framework.plugintool.Plugin; + +/** + * A configuration that includes all plugins on the classpath. + */ +public class DefaultPluginsConfiguration extends PluginsConfiguration { + @Override + protected boolean accepts(Class pluginClass) { + return true; + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java index 2bdb384995..1cc01add33 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginUtils.java @@ -33,48 +33,6 @@ import ghidra.util.exception.AssertException; */ public class PluginUtils { - /** - * Finds all {@link PluginDescription} objects that match a given set of plugin classes. This - * effectively tells the caller which of the given plugins have been loaded by the class loader. - *

- * eg: If the list of plugin classes contains the class "FooPlugin.class", this method - * will search the {@link PluginConfigurationModel} for any plugin with the name "FooPlugin" and - * return its {@link PluginDescription}. - *

- * Note that this method does not take path/package information into account when finding - * plugins; in the example above, if there is more than one plugin with the name "FooPlugin", - * only one will be found (the one found is not guaranteed to be the first). - * - * @param tool the current tool - * @param plugins the list of plugin classes to search for - * @return list of plugin descriptions - */ - public static List getPluginDescriptions(PluginTool tool, - List> plugins) { - - // First define the list of plugin descriptions to return - List retPlugins = new ArrayList<>(); - - // Get all plugins that have been loaded - PluginClassManager pluginClassManager = tool.getPluginClassManager(); - List allPluginDescriptions = - pluginClassManager.getManagedPluginDescriptions(); - - // see if an entry exists in the list of all loaded plugins - for (Class plugin : plugins) { - String pluginName = plugin.getSimpleName(); - - Optional desc = allPluginDescriptions.stream() - .filter(d -> (pluginName.equals(d.getName()))) - .findAny(); - if (desc.isPresent()) { - retPlugins.add(desc.get()); - } - } - - return retPlugins; - } - /** * Finds all plugin classes loaded from a given set of extensions. * diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginClassManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginsConfiguration.java similarity index 70% rename from Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginClassManager.java rename to Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginsConfiguration.java index e77146e3c7..587e2603fc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginClassManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginsConfiguration.java @@ -15,6 +15,8 @@ */ package ghidra.framework.plugintool.util; +import static java.util.function.Predicate.*; + import java.util.*; import java.util.function.Predicate; @@ -25,30 +27,33 @@ import ghidra.framework.plugintool.Plugin; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; -public class PluginClassManager { +/** + * This class maintains a collection of all plugin classes that are acceptable for a given tool + * type. Simple applications with only one plugin type can use the + * {@link DefaultPluginsConfiguration}. More complex tools can support a subset of the available + * plugins. Those tools should create custom subclasses for each tool type, that filter out plugins + * that are not appropriate for that tool type. + */ +public abstract class PluginsConfiguration { - private Map> packageMap = new HashMap<>(); + private Map> descriptionsByPackage = new HashMap<>(); + private Map descriptionsByName = new HashMap<>(); - private Map pluginClassMap = new HashMap<>(); - - public PluginClassManager(Class filterClass, Class exclusionClass) { - populatePluginDescriptionMaps(filterClass, exclusionClass); + protected PluginsConfiguration() { + populatePluginDescriptionMaps(); } - public PluginDescription getPluginDescription(String className) { - return pluginClassMap.get(className); + protected abstract boolean accepts(Class pluginClass); + + private Predicate> createFilter() { + Predicate> ignore = ProgramaticUseOnly.class::isAssignableFrom; + return not(ignore).and(c -> accepts(c)); } - private void populatePluginDescriptionMaps(Class localFilterClass, - Class localExclusionClass) { + private void populatePluginDescriptionMaps() { - Predicate> myClassFilter = - c -> (localFilterClass == null || localFilterClass.isAssignableFrom(c)) && - (localExclusionClass == null || !localExclusionClass.isAssignableFrom(c)) && - !ProgramaticUseOnly.class.isAssignableFrom(c); - - List> classes = - ClassSearcher.getClasses(Plugin.class, myClassFilter); + Predicate> classFilter = createFilter(); + List> classes = ClassSearcher.getClasses(Plugin.class, classFilter); for (Class pluginClass : classes) { if (!PluginUtils.isValidPluginClass(pluginClass)) { @@ -57,16 +62,21 @@ public class PluginClassManager { } PluginDescription pd = PluginDescription.getPluginDescription(pluginClass); - pluginClassMap.put(pluginClass.getName(), pd); + descriptionsByName.put(pluginClass.getName(), pd); PluginPackage pluginPackage = pd.getPluginPackage(); List list = - packageMap.computeIfAbsent(pluginPackage, (k) -> new ArrayList<>()); + descriptionsByPackage.computeIfAbsent(pluginPackage, (k) -> new ArrayList<>()); list.add(pd); } + } - public void addXmlElementsForPlugins(Element root, List plugins) { + public PluginDescription getPluginDescription(String className) { + return descriptionsByName.get(className); + } + + public void savePluginsToXml(Element root, List plugins) { Map> pluginPackageMap = buildPluginPackageMap(plugins); for (PluginPackage pluginPackage : pluginPackageMap.keySet()) { root.addContent(getPackageElement(pluginPackage, pluginPackageMap.get(pluginPackage))); @@ -76,7 +86,7 @@ public class PluginClassManager { private Element getPackageElement(PluginPackage pluginPackage, List pluginList) { Element packageElement = new Element("PACKAGE"); packageElement.setAttribute("NAME", pluginPackage.getName()); - List pluginDescriptions = packageMap.get(pluginPackage); + List pluginDescriptions = descriptionsByPackage.get(pluginPackage); Set includedPluginClasses = new HashSet<>(); for (Plugin plugin : pluginList) { @@ -120,7 +130,8 @@ public class PluginClassManager { private Map> buildPluginPackageMap(List plugins) { Map> pluginPackageMap = new HashMap<>(); for (Plugin plugin : plugins) { - PluginDescription pluginDescription = pluginClassMap.get(plugin.getClass().getName()); + PluginDescription pluginDescription = + descriptionsByName.get(plugin.getClass().getName()); if (pluginDescription == null) { continue; } @@ -138,41 +149,42 @@ public class PluginClassManager { } /** - * Used to convert an old style tool XML file by adding in classes in the same packages as - * those that were named specifically in the XML file + * Used to convert an old style tool XML file by mapping the given class names to plugin + * packages and then adding all plugins in that package. This has the effect of pulling + * in more plugin classes than were originally specified in the tool xml. + * * @param classNames the list of classNames from from the old XML file - * @return the adjusted class names + * @return the adjusted set of plugin class names */ - public Set fillInPackageClasses(List classNames) { + public Set getPluginNamesByCurrentPackage(List classNames) { Set packages = new HashSet<>(); Set adjustedClassNames = new HashSet<>(); for (String className : classNames) { - PluginDescription pluginDescription = pluginClassMap.get(className); - if (pluginDescription != null) { - if (pluginDescription.getStatus() == PluginStatus.RELEASED) { - packages.add(pluginDescription.getPluginPackage()); - } - else { - adjustedClassNames.add(className); - } + PluginDescription pd = descriptionsByName.get(className); + if (pd == null) { + continue; // plugin no longer in tool + } + + if (pd.getStatus() == PluginStatus.RELEASED) { + packages.add(pd.getPluginPackage()); + } + else { + adjustedClassNames.add(className); } } + for (PluginPackage pluginPackage : packages) { - List list = packageMap.get(pluginPackage); - for (PluginDescription pluginDescription : list) { - if (pluginDescription.getStatus() != PluginStatus.RELEASED) { - continue; - } - String name = pluginDescription.getPluginClass().getName(); - adjustedClassNames.add(name); + List packageDescriptions = descriptionsByPackage.get(pluginPackage); + for (PluginDescription pd : packageDescriptions) { + adjustedClassNames.add(pd.getPluginClass().getName()); } } return adjustedClassNames; } - public Set getPluginClasses(Element element) { + public Set getPluginClassNames(Element element) { Set classNames = new HashSet<>(); List children = element.getChildren("PACKAGE"); @@ -206,7 +218,8 @@ public class PluginClassManager { } PluginPackage pluginPackage = PluginPackage.getPluginPackage(packageName); - List pluginDescriptionList = packageMap.get(pluginPackage); + List pluginDescriptionList = + descriptionsByPackage.get(pluginPackage); if (pluginDescriptionList == null) { continue; } @@ -236,13 +249,13 @@ public class PluginClassManager { } public List getPluginPackages() { - List list = new ArrayList<>(packageMap.keySet()); + List list = new ArrayList<>(descriptionsByPackage.keySet()); Collections.sort(list); return list; } public List getPluginDescriptions(PluginPackage pluginPackage) { - List list = packageMap.get(pluginPackage); + List list = descriptionsByPackage.get(pluginPackage); List stableList = new ArrayList<>(); for (PluginDescription pluginDescription : list) { if (pluginDescription.getStatus() == PluginStatus.UNSTABLE || @@ -256,7 +269,7 @@ public class PluginClassManager { public List getUnstablePluginDescriptions() { List unstablePlugins = new ArrayList<>(); - for (PluginDescription pluginDescription : pluginClassMap.values()) { + for (PluginDescription pluginDescription : descriptionsByName.values()) { if (pluginDescription.getStatus() == PluginStatus.UNSTABLE) { unstablePlugins.add(pluginDescription); } @@ -266,7 +279,7 @@ public class PluginClassManager { public List getManagedPluginDescriptions() { ArrayList nonHiddenPlugins = new ArrayList<>(); - for (PluginDescription pluginDescription : pluginClassMap.values()) { + for (PluginDescription pluginDescription : descriptionsByName.values()) { if (pluginDescription.getStatus() == PluginStatus.HIDDEN) { continue; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraPluginsConfiguration.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraPluginsConfiguration.java new file mode 100644 index 0000000000..d6768de600 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraPluginsConfiguration.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.framework.project.tool; + +import ghidra.framework.main.ApplicationLevelOnlyPlugin; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.util.PluginsConfiguration; + +/** + * A configuration that allows all general plugins and application plugins. Plugins that may only + * exist at the application level are filtered out. + */ +class GhidraPluginsConfiguration extends PluginsConfiguration { + + @Override + protected boolean accepts(Class c) { + return !(ApplicationLevelOnlyPlugin.class.isAssignableFrom(c)); + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java index 2a4584ed73..63925e792d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java @@ -28,11 +28,11 @@ import docking.action.MenuData; import docking.tool.ToolConstants; import docking.widgets.OptionDialog; import ghidra.app.util.FileOpenDropHandler; -import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.model.Project; import ghidra.framework.model.ToolTemplate; import ghidra.framework.options.PreferenceState; -import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.PluginConfigurationModel; +import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.dialog.*; import ghidra.framework.plugintool.util.*; import ghidra.util.HelpLocation; @@ -55,9 +55,6 @@ public class GhidraTool extends PluginTool { public static boolean autoSave = true; private FileOpenDropHandler fileOpenDropHandler; - - private PluginClassManager pluginClassManager; - private DockingAction configureToolAction; /** @@ -99,12 +96,8 @@ public class GhidraTool extends PluginTool { } @Override - public PluginClassManager getPluginClassManager() { - if (pluginClassManager == null) { - pluginClassManager = - new PluginClassManager(Plugin.class, ApplicationLevelOnlyPlugin.class); - } - return pluginClassManager; + protected PluginsConfiguration createPluginsConfigurations() { + return new GhidraPluginsConfiguration(); } @Override @@ -254,8 +247,7 @@ public class GhidraTool extends PluginTool { int option = OptionDialog.showYesNoDialog(getActiveWindow(), "New Plugins Found!", "New extension plugins detected. Would you like to configure them?"); if (option == OptionDialog.YES_OPTION) { - List pluginDescriptions = - PluginUtils.getPluginDescriptions(this, newPlugins); + List pluginDescriptions = getPluginDescriptions(this, newPlugins); PluginInstallerDialog pluginInstaller = new PluginInstallerDialog("New Plugins Found!", this, new PluginConfigurationModel(this), pluginDescriptions); showDialog(pluginInstaller); @@ -265,6 +257,43 @@ public class GhidraTool extends PluginTool { addInstalledExtensions(newExtensions); } + /** + * Finds all {@link PluginDescription} objects that match a given set of plugin classes. This + * effectively tells the caller which of the given plugins have been loaded by the class loader. + *

+ * Note that this method does not take path/package information into account when finding + * plugins; in the example above, if there is more than one plugin with the name "FooPlugin", + * only one will be found (the one found is not guaranteed to be the first). + * + * @param tool the current tool + * @param plugins the list of plugin classes to search for + * @return list of plugin descriptions + */ + private List getPluginDescriptions(PluginTool tool, List> plugins) { + + // First define the list of plugin descriptions to return + List retPlugins = new ArrayList<>(); + + // Get all plugins that have been loaded + PluginsConfiguration pluginClassManager = getPluginsConfiguration(); + List allPluginDescriptions = + pluginClassManager.getManagedPluginDescriptions(); + + // see if an entry exists in the list of all loaded plugins + for (Class plugin : plugins) { + String pluginName = plugin.getSimpleName(); + + Optional desc = allPluginDescriptions.stream() + .filter(d -> (pluginName.equals(d.getName()))) + .findAny(); + if (desc.isPresent()) { + retPlugins.add(desc.get()); + } + } + + return retPlugins; + } + /** * Removes any extensions in the tool preferences that are no longer installed. */ diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/plugintool/DummyPluginTool.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/plugintool/DummyPluginTool.java index a6bfca21f4..d9ae45b15f 100644 --- a/Ghidra/Framework/Project/src/test/java/ghidra/framework/plugintool/DummyPluginTool.java +++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/plugintool/DummyPluginTool.java @@ -15,10 +15,10 @@ */ package ghidra.framework.plugintool; -import ghidra.framework.plugintool.util.PluginClassManager; +import ghidra.framework.plugintool.util.PluginsConfiguration; /** - * A dummy version of {@link PluginTool} that tests can use when they need an instance of + * A dummy version of {@link PluginTool} that tests can use when they need an instance of * the PluginTool, but do not wish to use a real version */ public class DummyPluginTool extends PluginTool { @@ -29,7 +29,7 @@ public class DummyPluginTool extends PluginTool { } @Override - public PluginClassManager getPluginClassManager() { + protected PluginsConfiguration createPluginsConfigurations() { return null; } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java index f722afcd1c..fbce527c74 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/test/DummyTool.java @@ -401,7 +401,7 @@ public class DummyTool extends PluginTool { @Override public void removeContextListener(DockingContextListener listener) { - //do nothing + //do nothing } @Override @@ -416,16 +416,16 @@ public class DummyTool extends PluginTool { @Override public void addServiceListener(ServiceListener listener) { - //do nothing + //do nothing } @Override public void removeServiceListener(ServiceListener listener) { - //do nothing + //do nothing } @Override - public PluginClassManager getPluginClassManager() { + protected PluginsConfiguration createPluginsConfigurations() { return null; }