From 7d81aacaa170c8df93abb9c50de1553f7b865e79 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 5 Aug 2022 12:02:36 -0400 Subject: [PATCH] GP-2386 - Refactor of plugin package loading to get 'core' classes to load by default --- .../model/DebuggerModelServicePlugin.java | 4 +- .../DebuggerWorkflowServicePlugin.java | 4 +- .../plugin/core/archive/ArchivePlugin.java | 2 +- .../EclipseIntegrationOptionsPlugin.java | 4 +- .../plugin/core/exporter/ExporterPlugin.java | 4 +- .../plugin/core/help/AboutProgramPlugin.java | 4 +- .../plugin/core/help/ProcessorListPlugin.java | 4 +- .../core/misc/RecoverySnapshotMgrPlugin.java | 4 +- .../processors/LanguageProviderPlugin.java | 4 +- .../plugin/core/totd/TipOfTheDayPlugin.java | 4 +- .../DomainFolderChangesDisplayPlugin.java | 4 +- .../debug/GenerateOldLanguagePlugin.java | 4 +- .../app/plugin/debug/JavaHelpPlugin.java | 4 +- .../app/plugin/gui/LookAndFeelPlugin.java | 4 +- .../plugin/importer/ImporterPlugin.java | 2 +- .../fsbrowser/FileSystemBrowserPlugin.java | 4 +- .../dialog/ManageFrontEndToolTest.java | 4 +- .../fileformats/FileFormatsPlugin.java | 4 +- .../Framework/Project/certification.manifest | 1 + .../app/plugin/debug/MemoryUsagePlugin.java | 19 +- .../app/plugin/debug/ShowMemoryDialog.java | 0 ...y.java => ApplicationLevelOnlyPlugin.java} | 9 +- ...dable.java => ApplicationLevelPlugin.java} | 13 +- .../ghidra/framework/main/FrontEndTool.java | 226 ++++++++++-------- .../framework/main/UtilityPluginPackage.java | 29 +++ .../ghidra/framework/plugintool/Plugin.java | 38 +-- .../framework/plugintool/PluginManager.java | 10 +- .../framework/plugintool/PluginTool.java | 79 +++--- .../plugintool/StandAloneApplication.java | 9 +- .../plugintool/util/PluginClassManager.java | 45 ++-- .../plugintool/util/PluginPackage.java | 3 +- .../framework/project/tool/GhidraTool.java | 4 +- .../images/applications-development-web.png | Bin 0 -> 3254 bytes 33 files changed, 315 insertions(+), 238 deletions(-) rename Ghidra/{Features/Base => Framework/Project}/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java (89%) rename Ghidra/{Features/Base => Framework/Project}/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java (100%) rename Ghidra/Framework/Project/src/main/java/ghidra/framework/main/{FrontEndOnly.java => ApplicationLevelOnlyPlugin.java} (74%) rename Ghidra/Framework/Project/src/main/java/ghidra/framework/main/{FrontEndable.java => ApplicationLevelPlugin.java} (61%) create mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/main/UtilityPluginPackage.java create mode 100644 Ghidra/Framework/Project/src/main/resources/images/applications-development-web.png diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 7d94e449ce..14e760eafa 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -45,7 +45,7 @@ import ghidra.dbg.*; import ghidra.dbg.target.*; import ghidra.dbg.util.PathUtils; import ghidra.framework.main.AppInfo; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -71,7 +71,7 @@ import ghidra.util.datastruct.ListenerSet; servicesProvided = { DebuggerModelService.class, }) public class DebuggerModelServicePlugin extends Plugin - implements DebuggerModelServiceInternal, FrontEndOnly { + implements DebuggerModelServiceInternal, ApplicationLevelOnlyPlugin { private static final String PREFIX_FACTORY = "Factory_"; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java index a3bb413495..a662a7d37d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java @@ -31,7 +31,7 @@ import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.*; import ghidra.dbg.DebuggerObjectModel; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.main.FrontEndTool; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; @@ -55,7 +55,7 @@ import ghidra.util.datastruct.CollectionChangeListener; } // ) public class DebuggerWorkflowServicePlugin extends Plugin - implements DebuggerWorkflowService, FrontEndOnly, OptionsChangeListener { + implements DebuggerWorkflowService, ApplicationLevelOnlyPlugin, OptionsChangeListener { protected class ForBotsModelsChangeListener implements CollectionChangeListener { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/ArchivePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/ArchivePlugin.java index bc2b5d8387..84d0faf741 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/ArchivePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/ArchivePlugin.java @@ -49,7 +49,7 @@ import ghidra.util.task.*; description = "The archive plugin provides a menu action from the project window allowing the user to archive a project or restore an archived project. " ) //@formatter:on -public class ArchivePlugin extends Plugin implements FrontEndOnly, ProjectListener { +public class ArchivePlugin extends Plugin implements ApplicationLevelOnlyPlugin, ProjectListener { private static final String PROJECT_GROUP_C_2 = "CProject2"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/eclipse/EclipseIntegrationOptionsPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/eclipse/EclipseIntegrationOptionsPlugin.java index c818294519..811d3545ae 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/eclipse/EclipseIntegrationOptionsPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/eclipse/EclipseIntegrationOptionsPlugin.java @@ -19,7 +19,7 @@ import java.io.File; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.options.OptionType; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.*; @@ -38,7 +38,7 @@ import ghidra.util.HelpLocation; description = "Options Eclipse Integration" ) //@formatter:on -public class EclipseIntegrationOptionsPlugin extends Plugin implements FrontEndOnly { +public class EclipseIntegrationOptionsPlugin extends Plugin implements ApplicationLevelOnlyPlugin { public static final String PLUGIN_OPTIONS_NAME = "Eclipse Integration"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterPlugin.java index c27a2a2635..f2391104da 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterPlugin.java @@ -24,7 +24,7 @@ import ghidra.app.context.NavigatableActionContext; import ghidra.app.context.NavigatableContextAction; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.CodeViewerService; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.main.datatable.ProjectDataContext; import ghidra.framework.main.datatable.FrontendProjectTreeAction; import ghidra.framework.model.DomainFile; @@ -44,7 +44,7 @@ import ghidra.util.HelpLocation; description = "This plugin exports a program or datatype archive to an external file." ) //@formatter:on -public class ExporterPlugin extends Plugin implements FrontEndable { +public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin { public ExporterPlugin(PluginTool tool) { super(tool); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java index 9201079e8b..0527cc18be 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java @@ -28,7 +28,7 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.util.GenericHelpTopics; import ghidra.app.util.HelpTopics; import ghidra.framework.main.FrontEndTool; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.main.datatable.FrontendProjectTreeAction; import ghidra.framework.main.datatable.ProjectDataContext; import ghidra.framework.model.DomainFile; @@ -50,7 +50,7 @@ import ghidra.util.HelpLocation; description = "This plugin provides an action that displays information about the currently loaded program" ) //@formatter:on -public class AboutProgramPlugin extends Plugin implements FrontEndable { +public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin { public final static String PLUGIN_NAME = "AboutProgramPlugin"; public final static String ACTION_NAME = "About Program"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/ProcessorListPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/ProcessorListPlugin.java index ab70d561ac..39abac0831 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/ProcessorListPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/ProcessorListPlugin.java @@ -34,7 +34,7 @@ import docking.widgets.table.GTable; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.util.HelpTopics; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.lang.*; @@ -51,7 +51,7 @@ import ghidra.util.SystemUtilities; description = "This plugin provides a Help action that displays a list of installed processor modules" ) //@formatter:on -public class ProcessorListPlugin extends Plugin implements FrontEndable { +public class ProcessorListPlugin extends Plugin implements ApplicationLevelPlugin { private DockingAction processorListAction; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/misc/RecoverySnapshotMgrPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/misc/RecoverySnapshotMgrPlugin.java index b201209a1f..b3302be4ba 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/misc/RecoverySnapshotMgrPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/misc/RecoverySnapshotMgrPlugin.java @@ -24,7 +24,7 @@ import javax.swing.Timer; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.main.FrontEndTool; import ghidra.framework.model.*; import ghidra.framework.options.OptionsChangeListener; @@ -49,7 +49,7 @@ import ghidra.util.exception.AssertException; ) //@formatter:on public class RecoverySnapshotMgrPlugin extends Plugin - implements FrontEndOnly, OptionsChangeListener, ProjectListener { + implements ApplicationLevelOnlyPlugin, OptionsChangeListener, ProjectListener { private final static String OPTIONS_TITLE = "Recovery"; private final static String SNAPSHOT_PERIOD_OPTION = "Snapshot period (minutes, 0=disabled)"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/LanguageProviderPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/LanguageProviderPlugin.java index f885114700..2dbf91aba7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/LanguageProviderPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/LanguageProviderPlugin.java @@ -27,7 +27,7 @@ import docking.widgets.OptionDialog; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.framework.main.FrontEndTool; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.main.datatable.ProjectDataContext; import ghidra.framework.model.*; import ghidra.framework.plugintool.*; @@ -52,7 +52,7 @@ import ghidra.util.task.*; description = "This plugin provides the set language feature." ) //@formatter:on -public final class LanguageProviderPlugin extends Plugin implements FrontEndable { +public final class LanguageProviderPlugin extends Plugin implements ApplicationLevelPlugin { private DockingAction setLanguageAction; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/totd/TipOfTheDayPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/totd/TipOfTheDayPlugin.java index 353faf4687..06640b9cbc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/totd/TipOfTheDayPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/totd/TipOfTheDayPlugin.java @@ -26,7 +26,7 @@ import docking.action.MenuData; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.preferences.Preferences; @@ -43,7 +43,7 @@ import utilities.util.FileUtilities; description = "Display a dialog containing 'Tips of the Day'." ) //@formatter:on -public class TipOfTheDayPlugin extends Plugin implements FrontEndOnly { +public class TipOfTheDayPlugin extends Plugin implements ApplicationLevelOnlyPlugin { private static final String TIP_INDEX = "TIP_INDEX"; private static final String SHOW_TIPS = "SHOW_TIPS"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java index de79e6fa4b..762577a456 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java @@ -17,7 +17,7 @@ package ghidra.app.plugin.debug; import ghidra.app.DeveloperPluginPackage; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.main.FrontEndService; import ghidra.framework.model.*; import ghidra.framework.plugintool.*; @@ -34,7 +34,7 @@ import ghidra.framework.plugintool.util.PluginStatus; ) //@formatter:on public class DomainFolderChangesDisplayPlugin extends Plugin - implements FrontEndOnly, ProjectListener, DomainFolderChangeListener { + implements ApplicationLevelOnlyPlugin, ProjectListener, DomainFolderChangeListener { private DomainFolderChangesDisplayComponentProvider provider; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/GenerateOldLanguagePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/GenerateOldLanguagePlugin.java index 8da7f2655c..9999744a58 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/GenerateOldLanguagePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/GenerateOldLanguagePlugin.java @@ -40,7 +40,7 @@ import ghidra.app.DeveloperPluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.util.bean.SelectLanguagePanel; import ghidra.framework.Application; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.AddressSpace; @@ -64,7 +64,7 @@ import ghidra.util.xml.GenericXMLOutputter; "language which will modify its address spaces or register definitions." ) //@formatter:on -public class GenerateOldLanguagePlugin extends Plugin implements FrontEndable { +public class GenerateOldLanguagePlugin extends Plugin implements ApplicationLevelPlugin { private static final ExtensionFileFilter OLD_LANG_FILTER = new ExtensionFileFilter("lang", "Old Language File"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/JavaHelpPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/JavaHelpPlugin.java index 631238e07c..af2480d9a6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/JavaHelpPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/JavaHelpPlugin.java @@ -30,7 +30,7 @@ import docking.help.*; import docking.tool.ToolConstants; import ghidra.app.DeveloperPluginPackage; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.model.Project; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -52,7 +52,7 @@ import ghidra.util.task.*; JavaHelpPlugin.infoName + ", is written to your home directory." ) //@formatter:on -public class JavaHelpPlugin extends Plugin implements FrontEndable { +public class JavaHelpPlugin extends Plugin implements ApplicationLevelPlugin { static final String infoName = "GhidraHelpInfo.txt"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java index 3101b8ec85..b71ec19dda 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java @@ -25,7 +25,7 @@ import docking.widgets.OptionDialog; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.docking.util.DockingWindowsLookAndFeelUtils; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.main.FrontEndTool; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; @@ -44,7 +44,7 @@ import ghidra.util.*; "only in the Ghidra Project Window." ) //@formatter:on -public class LookAndFeelPlugin extends Plugin implements FrontEndOnly, OptionsChangeListener { +public class LookAndFeelPlugin extends Plugin implements ApplicationLevelOnlyPlugin, OptionsChangeListener { private String selectedLookAndFeel; private boolean useInvertedColors; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java index e9173b83fa..c986188f9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java @@ -73,7 +73,7 @@ import ghidra.util.task.TaskLauncher; ) //@formatter:on public class ImporterPlugin extends Plugin - implements FileImporterService, FrontEndable, ProjectListener { + implements FileImporterService, ApplicationLevelPlugin, ProjectListener { private static final String IMPORT_MENU_GROUP = "Import"; static final String IMPORTER_PLUGIN_DESC = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java index e72218f650..feb8a01738 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java @@ -38,7 +38,7 @@ import ghidra.app.services.*; import ghidra.app.util.opinion.LoaderService; import ghidra.formats.gfilesystem.*; import ghidra.framework.main.FrontEndService; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.model.Project; import ghidra.framework.model.ProjectListener; import ghidra.framework.plugintool.*; @@ -70,7 +70,7 @@ import utilities.util.FileUtilities; eventsConsumed = { ProgramActivatedPluginEvent.class } ) //@formatter:on -public class FileSystemBrowserPlugin extends Plugin implements FrontEndable, ProjectListener, +public class FileSystemBrowserPlugin extends Plugin implements ApplicationLevelPlugin, ProjectListener, FileSystemBrowserService { /* package */ DockingAction openFilesystemAction; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManageFrontEndToolTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManageFrontEndToolTest.java index fa08427e86..1b8e0b37ce 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManageFrontEndToolTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/ManageFrontEndToolTest.java @@ -67,11 +67,11 @@ public class ManageFrontEndToolTest extends AbstractGhidraHeadedIntegrationTest int count = 0; List allPluginDescriptions = pluginModel.getAllPluginDescriptions(); for (PluginDescription pluginDescription : allPluginDescriptions) { - assertTrue(FrontEndable.class.isAssignableFrom(pluginDescription.getPluginClass())); + assertTrue(ApplicationLevelPlugin.class.isAssignableFrom(pluginDescription.getPluginClass())); count++; } - List> classes = ClassSearcher.getClasses(FrontEndable.class); + List> classes = ClassSearcher.getClasses(ApplicationLevelPlugin.class); assertEquals(count, classes.size()); } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java index f34a4aee3d..4766899230 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java @@ -38,7 +38,7 @@ import ghidra.file.formats.ios.prelink.MachoPrelinkFileSystem; import ghidra.file.jad.JadProcessWrapper; import ghidra.file.jad.JarDecompiler; import ghidra.formats.gfilesystem.*; -import ghidra.framework.main.FrontEndable; +import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.plugins.fsbrowser.*; @@ -60,7 +60,7 @@ import ghidra.util.task.TaskMonitor; description = "This plugin provides file format related actions to the File System Browser." ) //@formatter:on -public class FileFormatsPlugin extends Plugin implements FrontEndable { +public class FileFormatsPlugin extends Plugin implements ApplicationLevelPlugin { private GhidraFileChooser chooserEclipse; private GhidraFileChooser chooserJarFolder; diff --git a/Ghidra/Framework/Project/certification.manifest b/Ghidra/Framework/Project/certification.manifest index ec923064e2..ab5289e0bc 100644 --- a/Ghidra/Framework/Project/certification.manifest +++ b/Ghidra/Framework/Project/certification.manifest @@ -20,6 +20,7 @@ src/main/java/ghidra/framework/project/tool/package.html||GHIDRA||||END| src/main/resources/images/Minus.png||GHIDRA||||END| src/main/resources/images/Plus.png||GHIDRA||||END| src/main/resources/images/allInTool.gif||GHIDRA||reviewed||END| +src/main/resources/images/applications-development-web.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/applications-science.png||Oxygen Icons - LGPL 3.0||||END| src/main/resources/images/bug.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/check.png||GHIDRA||reviewed||END| diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java similarity index 89% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java rename to Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java index 9c11536146..8e50566fef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java @@ -15,32 +15,29 @@ */ package ghidra.app.plugin.debug; -import ghidra.app.CorePluginPackage; -import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.main.FrontEndOnly; -import ghidra.framework.plugintool.*; -import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.util.HelpLocation; import docking.ActionContext; import docking.DialogComponentProvider; import docking.action.DockingAction; import docking.action.MenuData; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; +import ghidra.framework.main.UtilityPluginPackage; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.util.HelpLocation; //@formatter:off @PluginInfo( status = PluginStatus.RELEASED, - packageName = CorePluginPackage.NAME, + packageName = UtilityPluginPackage.NAME, category = PluginCategoryNames.SUPPORT, shortDescription = "VM Memory Display", description = "Plugin for displaying the VM memory information." ) //@formatter:on -public class MemoryUsagePlugin extends Plugin implements FrontEndOnly { +public class MemoryUsagePlugin extends Plugin implements ApplicationLevelOnlyPlugin { private DialogComponentProvider dialog; - /** - * Constructor - */ public MemoryUsagePlugin(PluginTool tool) { super(tool); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java similarity index 100% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java rename to Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndOnly.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelOnlyPlugin.java similarity index 74% rename from Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndOnly.java rename to Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelOnlyPlugin.java index 17688386b2..57518e859e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndOnly.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelOnlyPlugin.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. @@ -16,10 +15,10 @@ */ package ghidra.framework.main; - /** - * Marker interface to indicate plugin is for Front-end use only. + * Marker interface to indicate this plugin is application-level tools only (see + * {@link ApplicationLevelPlugin}). */ -public interface FrontEndOnly extends FrontEndable { - +public interface ApplicationLevelOnlyPlugin extends ApplicationLevelPlugin { + // marker interface } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndable.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPlugin.java similarity index 61% rename from Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndable.java rename to Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPlugin.java index f6f989659c..f66941166d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndable.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ApplicationLevelPlugin.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,10 +16,12 @@ package ghidra.framework.main; /** - * Marker that allows a plugin to be added directly to the Ghidra Project - * Window by the user. - * Some plugins may get added due to dependencies by FrontEndable - * plugins. + * Marker that signals the implementing plugin can be added to the system at the application level. + *

+ * Some applications have only a single tool while other applications may have multiple tools, with + * a top-level tool that manages other sub-tools. A plugin implementing this interface can be used + * in any of these tools. */ -public interface FrontEndable { +public interface ApplicationLevelPlugin { + // marker interface } 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 9f688a8971..340987cc21 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 @@ -27,6 +27,7 @@ import java.util.Set; import javax.swing.JFrame; import javax.swing.JPanel; +import org.apache.commons.lang3.StringUtils; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; @@ -108,8 +109,11 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { private final static String GHIDRA_MAIN_PANEL_DIVIDER_LOC = "GhidraMainPanelDividerLocation"; private static final String FRONT_END_TOOL_XML_NAME = "FRONTEND"; + private static final String VERSION_ATTRIBUTE_NAME = "VERSION"; private static final String FRONT_END_FILE_NAME = "FrontEndTool.xml"; private static final String CONFIGURE_GROUP = "Configure"; + private static final File TOOL_FILE = + new File(Application.getUserSettingsDirectory(), FRONT_END_FILE_NAME); private WeakSet listeners; private FrontEndPlugin plugin; @@ -123,7 +127,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Construct a new Ghidra Project Window. - * + * * @param pm project manager */ public FrontEndTool(ProjectManager pm) { @@ -132,7 +136,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); - addFrontEndPlugin(); + installFrontEndPlugins(); createActions(); loadToolConfigurationFromDisk(); @@ -164,7 +168,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { @Override public PluginClassManager getPluginClassManager() { if (pluginClassManager == null) { - pluginClassManager = new PluginClassManager(FrontEndable.class, null); + pluginClassManager = new PluginClassManager(ApplicationLevelPlugin.class, null); } return pluginClassManager; } @@ -174,46 +178,97 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { } private void loadToolConfigurationFromDisk() { - File saveFile = new File(Application.getUserSettingsDirectory(), FRONT_END_FILE_NAME); - if (!saveFile.exists()) { - addFrontEndablePlugins(); + + Element root = getToolFileXml(); + if (root == null) { + installDefaultApplicationLevelPlugins(); return; } - try { - InputStream is = new FileInputStream(saveFile); - SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false); - Element root = sax.build(is).getRootElement(); - GhidraToolTemplate template = new GhidraToolTemplate( - (Element) root.getChildren().get(0), saveFile.getAbsolutePath()); - refresh(template); + GhidraToolTemplate template = new GhidraToolTemplate((Element) root.getChildren().get(0), + TOOL_FILE.getAbsolutePath()); + refresh(template); + } + + private Element getToolFileXml() { + if (!TOOL_FILE.exists()) { + return null; } - catch (JDOMException e) { - Msg.showError(this, null, "Error", "Error in XML reading front end configuration", e); + + try (InputStream is = new FileInputStream(TOOL_FILE)) { + SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false); + return sax.build(is).getRootElement(); } - catch (IOException e) { + catch (IOException | JDOMException e) { Msg.showError(this, null, "Error", "Error reading front end configuration", e); } + return null; } void saveToolConfigurationToDisk() { ToolTemplate template = saveToolToToolTemplate(); Element root = new Element(FRONT_END_TOOL_XML_NAME); + + String version = Application.getApplicationVersion(); + root.setAttribute(VERSION_ATTRIBUTE_NAME, version); root.addContent(template.saveToXml()); - File saveFile = new File(Application.getUserSettingsDirectory(), FRONT_END_FILE_NAME); - try { - OutputStream os = new FileOutputStream(saveFile); + try (OutputStream os = new FileOutputStream(TOOL_FILE)) { org.jdom.Document doc = new org.jdom.Document(root); XMLOutputter xmlOut = new GenericXMLOutputter(); xmlOut.output(doc, os); - os.close(); } catch (IOException e) { Msg.showError(this, null, "Error", "Error saving front end configuration", e); } } - private void addFrontEndPlugin() { + private void installFrontEndPlugins() { + installFrontEndPlugin(); + + // manually install for old tool versions that have no knowledge of utility plugins + if (isPreUtilityGhidraVersion()) { + installUtilityPlugins(); + } + } + + private boolean isPreUtilityGhidraVersion() { + + Element root = getToolFileXml(); + String version = root.getAttributeValue(VERSION_ATTRIBUTE_NAME); + + // Note: any version implies the tool is newer than the addition of the 'version' + // attribute. In that case, the utility plugins are managed by the xml. + return StringUtils.isBlank(version); + } + + /** + * Add those plugins that implement the ApplicationLevelPlugin interface and have a + * RELEASED status and not (example || testing) category. + */ + private void installDefaultApplicationLevelPlugins() { + List classNames = new ArrayList<>(); + for (Class pluginClass : ClassSearcher.getClasses(Plugin.class, + c -> ApplicationLevelPlugin.class.isAssignableFrom(c))) { + + PluginDescription pd = PluginDescription.getPluginDescription(pluginClass); + String category = pd.getCategory(); + boolean isBadCategory = category.equals(GenericPluginCategoryNames.EXAMPLES) || + category.equals(GenericPluginCategoryNames.TESTING); + if (pd.getStatus() == PluginStatus.RELEASED && !isBadCategory) { + classNames.add(pluginClass.getName()); + } + } + + try { + addPlugins(classNames); + } + catch (PluginException e) { + Msg.showError(this, getToolFrame(), "Plugin Error", "Error restoring front-end plugins", + e); + } + } + + private void installFrontEndPlugin() { plugin = new FrontEndPlugin(this); plugin.setProjectManager(getProjectManager()); try { @@ -280,7 +335,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Set the active project. - * + * * @param project may be null if there is no active project */ public void setActiveProject(Project project) { @@ -301,7 +356,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Add the given project listener. - * + * * @param l listener to add */ public void addProjectListener(ProjectListener l) { @@ -310,7 +365,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Remove the given project listener. - * + * * @param l listener to remove */ public void removeProjectListener(ProjectListener l) { @@ -319,53 +374,54 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * NOTE: do not call this from a non-Swing thread - * + * * @param tool the tool * @return true if the repository is null or is connected. */ boolean checkRepositoryConnected(PluginTool tool) { RepositoryAdapter repository = tool.getProject().getRepository(); - if (repository != null) { - if (!repository.verifyConnection()) { - if (OptionDialog.showYesNoDialog(tool.getToolFrame(), "Lost Connection to Server", - "The connection to the Ghidra Server has been lost.\n" + - "Do you want to reconnect now?") == OptionDialog.OPTION_ONE) { - try { - repository.connect(); - return true; - } - catch (NotConnectedException e) { - // message displayed by repository server adapter - return false; - } - catch (IOException e) { - ClientUtil.handleException(repository, e, "Repository Connection", - tool.getToolFrame()); - return false; - } - } + if (repository == null) { + return true; + } + if (repository.verifyConnection()) { + return true; + } + + if (OptionDialog.showYesNoDialog(tool.getToolFrame(), "Lost Connection to Server", + "The connection to the Ghidra Server has been lost.\n" + + "Do you want to reconnect now?") == OptionDialog.OPTION_ONE) { + try { + repository.connect(); + return true; + } + catch (NotConnectedException e) { + // message displayed by repository server adapter + return false; + } + catch (IOException e) { + ClientUtil.handleException(repository, e, "Repository Connection", + tool.getToolFrame()); return false; } } - return true; + + return false; } /** * Check in the given domain file. - * + * * @param tool tool that has the domain file opened * @param domainFile domain file to check in */ public void checkIn(PluginTool tool, DomainFile domainFile) { - ArrayList list = new ArrayList<>(); - list.add(domainFile); - checkIn(tool, list, tool.getToolFrame()); + checkIn(tool, List.of(domainFile), tool.getToolFrame()); } /** * Check in the list of domain files. - * + * * @param tool tool that has the domain files opened * @param fileList list of DomainFile objects * @param parent parent of dialog if an error occurs during checkin @@ -378,8 +434,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { ArrayList changedList = new ArrayList<>(); ArrayList list = new ArrayList<>(); - for (int i = 0; i < fileList.size(); i++) { - DomainFile df = fileList.get(i); + for (DomainFile df : fileList) { if (df != null && df.canCheckin()) { if (!canCloseDomainFile(df)) { continue; @@ -417,7 +472,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { * Merge the latest version in the repository with the given checked out * domain file. Upon completion of the merge, the domain file appears as * though the latest version was checked out. - * + * * @param tool tool that has the domain file opened * @param domainFile domain file where latest version will be merged into * @param taskListener listener that is notified when the merge task @@ -433,7 +488,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { * Merge the latest version (in the repository) of each checked out file in * fileList. Upon completion of the merge, the domain file appears as though * the latest version was checked out. - * + * * @param tool tool that has the domain files opened * @param fileList list of files that are checked out and are to be merged * @param taskListener listener that is notified when the merge task @@ -447,8 +502,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { ArrayList list = new ArrayList<>(); ArrayList changedList = new ArrayList<>(); - for (int i = 0; i < fileList.size(); i++) { - DomainFile df = fileList.get(i); + for (DomainFile df : fileList) { if (df != null && df.canMerge()) { if (!canCloseDomainFile(df)) { continue; @@ -531,8 +585,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { menuData.setMenuSubGroup(CONFIGURE_GROUP + 2); installExtensionsAction.setMenuBarData(menuData); - installExtensionsAction.setHelpLocation( - new HelpLocation(GenericHelpTopics.FRONT_END, "Extensions")); + installExtensionsAction + .setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "Extensions")); installExtensionsAction.setEnabled(true); addAction(installExtensionsAction); } @@ -558,8 +612,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { menuData.setMenuSubGroup(CONFIGURE_GROUP + 1); configureToolAction.setMenuBarData(menuData); - configureToolAction.setHelpLocation( - new HelpLocation(GenericHelpTopics.FRONT_END, "Configure")); + configureToolAction + .setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "Configure")); configureToolAction.setEnabled(true); addAction(configureToolAction); } @@ -571,11 +625,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { return toolTemplate; } - ////////////////////////////////////////////////////////////////////// - /** * Get project listeners. - * + * * @return ProjectListener[] */ Iterable getListeners() { @@ -597,50 +649,14 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { plugin.readDataState(saveState); } - //////////////////////////////////////////////////////////////////// - /** - * Add those plugins that implement the FrontEndable interface and have a - * RELEASED status and not (example || testing) category. - */ - private void addFrontEndablePlugins() { - List classNames = new ArrayList<>(); - for (Class pluginClass : ClassSearcher.getClasses(Plugin.class, - c -> FrontEndable.class.isAssignableFrom(c))) { - - PluginDescription pd = PluginDescription.getPluginDescription(pluginClass); - String category = pd.getCategory(); - boolean isBadCategory = category.equals(GenericPluginCategoryNames.EXAMPLES) || - category.equals(GenericPluginCategoryNames.TESTING); - if (pd.getStatus() == PluginStatus.RELEASED && !isBadCategory) { - classNames.add(pluginClass.getName()); - } - } - - try { - addPlugins(classNames.toArray(new String[classNames.size()])); - } - catch (PluginException e) { - Msg.showError(this, getToolFrame(), "Plugin Error", "Error restoring front-end plugins", - e); - } - } - - /** - * Refresh the plugins in the Ghidra Project Window based on what is - * contained in the given XML Element. - * - * @param tc object that contains an entry for each plugin and its - * configuration state + * Refresh the plugins in the Ghidra Project Window based on what is contained in the given XML + * Element. + * + * @param tc object that contains an entry for each plugin and its configuration state */ private void refresh(ToolTemplate tc) { listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); - List list = getManagedPlugins(); - list.remove(plugin); - Plugin[] plugins = new Plugin[list.size()]; - plugins = list.toArray(plugins); - removePlugins(plugins); - Element root = tc.saveToXml(); Element elem = root.getChild("TOOL"); @@ -691,7 +707,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Get the int value for the given string. - * + * * @param value the string value to parse * @param defaultValue return this value if a NumberFormatException is * thrown during the parseInt() method @@ -866,7 +882,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { /** * Construct a new MergeTask. - * + * * @param tool tool that has the domain files open * @param list list of DomainFiles to be merged * @param taskListener listener that is notified when this task diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/UtilityPluginPackage.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/UtilityPluginPackage.java new file mode 100644 index 0000000000..17e4196024 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/UtilityPluginPackage.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.util.PluginPackage; +import resources.ResourceManager; + +public class UtilityPluginPackage extends PluginPackage { + + public static final String NAME = "Utility"; + + public UtilityPluginPackage() { + super(NAME, ResourceManager.loadImage("images/applications-development-web.png"), + "These plugins provide basic functionality useful to all tools.", UTILITY_PRIORITY); + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java index 16617d98bb..b96c19ebfc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java @@ -72,7 +72,7 @@ import ghidra.util.classfinder.ExtensionPoint; * {@link PluginTool#getOptions(String)}. (optional)
* *

  • Other Plugins are constructed, dependencies evaluated, etc.
    - * If your dependencies are not available (ie. not installed, threw an exception during their + * If your dependencies are not available (i.e., not installed, threw an exception during their * initialization, etc), your Plugin's {@link #dispose()} will be called and then your Plugin * instance will be discarded.
    *
  • Your Plugin's {@link #init()} method is called (when its dependencies are met). @@ -87,8 +87,9 @@ import ghidra.util.classfinder.ExtensionPoint; *
  • ...user uses Ghidra... *
      *
    • Your Plugin's {@link #processEvent(PluginEvent)} is called for events. - *
    • Your Plugin's Action's methods (ie. - * {@link DockingAction#actionPerformed(docking.ActionContext) actionPerformed}) are called. + *
    • Your Plugin's Action's methods (i.e., + * {@link DockingAction#actionPerformed(docking.ActionContext) actionPerformed}) are + * called. *
    • Your Plugin's published service methods are called by other Plugins. *
    • Your Plugin's listener methods are called. *
    @@ -113,12 +114,12 @@ import ghidra.util.classfinder.ExtensionPoint; * calls the {@link PluginTool#getService(Class)} method. *

    * Conversely, any services your Plugin advertises in @PluginInfo must be published via calls to - * {@link #registerServiceProvided(Class, Object) registerServiceProvided()} in your Plugin's + * {@link #registerServiceProvided(Class, Object) registerServiceProvided()} in your Plugin's * constructor. *

    - * Cyclic dependencies are not allowed and will cause the Plugin management code to fail to - * load your Plugin. (ie. PluginA requires a service that PluginB provides, which requires a service - * that PluginA provides) + * Cyclic dependencies are not allowed and will cause the Plugin management code to fail to + * load your Plugin. (i.e., PluginA requires a service that PluginB provides, which requires a + * service that PluginA provides) * *

    Plugin Service implementation

    * A Plugin may provide a service to other Plugins by advertising in its {@link PluginInfo} @@ -161,14 +162,15 @@ import ghidra.util.classfinder.ExtensionPoint; * *

    Plugin Events

    *
      - *
    • Every type of plugin event should be represented by some class extending {@link PluginEvent}. - *
    • One PluginEvent subclass may be used for more than one event type as long as there's some + *
    • Every type of plugin event should be represented by some class extending + * {@link PluginEvent}. + *
    • One PluginEvent subclass may be used for more than one event type as long as there's some * natural grouping. *
    * *

    Component Providers

    *
      - *
    • A plugin may supply a {@link ComponentProvider} that provides a visual component when + *
    • A plugin may supply a {@link ComponentProvider} that provides a visual component when * the plugin is added to the tool. *
    * @@ -176,10 +178,10 @@ import ghidra.util.classfinder.ExtensionPoint; *
      *
    • {@link OptionsChangeListener} - to receive notification when a configuration option * is changed by the user. - *
    • {@link FrontEndable} - marks this Plugin as being suitable for inclusion in the FrontEnd - * tool. - *
    • {@link FrontEndOnly} - marks this Plugin as FrontEnd only, not usable in CodeBrowser or - * other tools. + *
    • {@link ApplicationLevelPlugin} - marks this Plugin as being suitable for inclusion in the + * application-level tool. + *
    • {@link ApplicationLevelOnlyPlugin} - marks this Plugin as application-level only, not + * usable in an application's sub-tools. *
    • {@link ProgramaticUseOnly} - marks this Plugin as special and not for user configuration. *
    * @@ -251,7 +253,7 @@ public abstract class Plugin implements ExtensionPoint, PluginEventListener, Ser } /** - * Auto-registers any services directly implemented by this Plugin instance (ie. + * Auto-registers any services directly implemented by this Plugin instance (i.e., * the MyService in "class MyPlugin extends Plugin implements MyService { }" ) */ private void registerPluginImplementedServices() { @@ -377,7 +379,7 @@ public abstract class Plugin implements ExtensionPoint, PluginEventListener, Ser } /** - * Called after the constructor and before {@link #init()} to publish services to + * Called after the constructor and before {@link #init()} to publish services to * the Tool's service registry. *

    * Services registered during the constructor call will be queued in the services list. @@ -520,7 +522,7 @@ public abstract class Plugin implements ExtensionPoint, PluginEventListener, Ser /** * Check if this plugin depends on the given plugin - * + * * @param plugin the plugin * @return true if this plugin depends on the given plugin */ @@ -836,7 +838,7 @@ public abstract class Plugin implements ExtensionPoint, PluginEventListener, Ser * Close the plugin. This is when the plugin should release resources, such as those from * other services. This method should not close resources being used by others (that should * happen in dispose()). - * + * *

    This method will be called before {@link #dispose()}. */ protected void close() { 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 e5330629f8..7308f92f80 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 @@ -86,7 +86,7 @@ class PluginManager { addPlugins(new Plugin[] { plugin }); } - void addPlugins(List classNames) throws PluginException { + void addPlugins(Collection classNames) throws PluginException { PluginException pe = null; List list = new ArrayList<>(classNames.size()); List badList = new ArrayList<>(); @@ -259,8 +259,8 @@ class PluginManager { void restorePluginsFromXml(Element root) throws PluginException { boolean isOld = isOldToolConfig(root); - List classNames = - isOld ? getPLuginClassNamesFromOldXml(root) : getPluginClassNamesToLoad(root); + Collection classNames = + isOld ? getPluginClassNamesFromOldXml(root) : getPluginClassNamesToLoad(root); Map map = isOld ? getPluginSavedStates(root, "PLUGIN") : getPluginSavedStates(root, "PLUGIN_STATE"); @@ -295,7 +295,7 @@ class PluginManager { return map; } - private List getPLuginClassNamesFromOldXml(Element root) { + private Set getPluginClassNamesFromOldXml(Element root) { List classNames = new ArrayList<>(); List pluginElementList = root.getChildren("PLUGIN"); Iterator iter = pluginElementList.iterator(); @@ -312,7 +312,7 @@ class PluginManager { return root.getChild("PLUGIN") != null; } - private List getPluginClassNamesToLoad(Element root) { + private Set getPluginClassNamesToLoad(Element root) { PluginClassManager pluginClassManager = tool.getPluginClassManager(); return pluginClassManager.getPluginClasses(root); } 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 8d2b710765..20e34a5d0d 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 @@ -47,8 +47,7 @@ import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.cmd.Command; -import ghidra.framework.main.AppInfo; -import ghidra.framework.main.UserAgreementDialog; +import ghidra.framework.main.*; import ghidra.framework.model.*; import ghidra.framework.options.*; import ghidra.framework.plugintool.dialog.ExtensionTableProvider; @@ -226,6 +225,34 @@ public abstract class PluginTool extends AbstractDockingTool { winMgr.setHomeButton(homeIcon, callback); } + /** + * Loads all application-level utility classes into this tool. This should only be called + * by tools that represent the global application tool and not for sub-tools. + */ + 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); + } + catch (PluginException e) { + Msg.showError(this, null, "Error Adding Utility Plugins", + "Unexpected exception adding application utility plugins", e); + } + } + /** * Placeholder for subclasses to get a chance to install actions before plugins. * @@ -796,9 +823,7 @@ public abstract class PluginTool extends AbstractDockingTool { * class already exists in the tool */ public void addPlugin(String className) throws PluginException { - checkedRunSwingNow(() -> { - addPlugins(new String[] { className }); - }, PluginException.class); + addPlugins(List.of(className)); } /** @@ -807,37 +832,36 @@ public abstract class PluginTool extends AbstractDockingTool { * @throws PluginException if a plugin could not be constructed, or * there was problem executing its init() method, or if a plugin of this * class already exists in the tool - * @deprecated use {@link #addPlugins(List)} + * @deprecated use {@link #addPlugins(Collection)} */ @Deprecated(since = "10.2", forRemoval = true) public void addPlugins(String[] classNames) throws PluginException { - try { - pluginMgr.addPlugins(Arrays.asList(classNames)); - } - finally { - setConfigChanged(true); - } + addPlugins(Arrays.asList(classNames)); } /** * Add plugins to the tool. - * @param classNames array of plugin class names + * @param classNames collection of plugin class names * @throws PluginException if a plugin could not be constructed, or * there was problem executing its init() method, or if a plugin of this * class already exists in the tool */ - public void addPlugins(List classNames) throws PluginException { - try { - pluginMgr.addPlugins(classNames); - } - finally { - setConfigChanged(true); - } + public void addPlugins(Collection classNames) throws PluginException { + checkedRunSwingNow(() -> { + try { + pluginMgr.addPlugins(classNames); + } + finally { + setConfigChanged(true); + } + }, PluginException.class); } public void addPlugin(Plugin p) throws PluginException { - pluginMgr.addPlugin(p); - setConfigChanged(true); + checkedRunSwingNow(() -> { + pluginMgr.addPlugin(p); + setConfigChanged(true); + }, PluginException.class); } /** @@ -847,14 +871,7 @@ public abstract class PluginTool extends AbstractDockingTool { */ @Deprecated(since = "10.2", forRemoval = true) public void removePlugins(Plugin[] plugins) { - SystemUtilities.runSwingNow(() -> { - try { - pluginMgr.removePlugins(Arrays.asList(plugins)); - } - finally { - setConfigChanged(true); - } - }); + removePlugins(Arrays.asList(plugins)); } /** @@ -862,7 +879,7 @@ public abstract class PluginTool extends AbstractDockingTool { * @param plugins array of plugins to remove */ public void removePlugins(List plugins) { - SystemUtilities.runSwingNow(() -> { + Swing.runNow(() -> { try { pluginMgr.removePlugins(plugins); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java index ef7041b8e8..0db4322fb1 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java @@ -31,7 +31,7 @@ import docking.framework.*; import ghidra.framework.*; import ghidra.framework.model.ToolServices; import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.CancelledException; import ghidra.util.xml.GenericXMLOutputter; @@ -56,7 +56,7 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat * {@link ApplicationProperties#APPLICATION_NAME_PROPERTY} and * {@link ApplicationProperties#APPLICATION_VERSION_PROPERTY} properties * set. - * + * * @param propertiesFilename the name of the properties file. * @throws IOException error causing application initialization failure */ @@ -76,7 +76,7 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat } /** - * reates a new application using the given application layout + * Creates a new application using the given application layout * and associated application properties. * @param applicationLayout application layout */ @@ -155,7 +155,7 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat setDockIcon(); try { - SystemUtilities.runSwingNow(() -> tool = createTool()); + Swing.runNow(() -> tool = createTool()); } catch (Exception e) { Msg.error(this, "Error creating tool, exiting...", e); @@ -201,6 +201,7 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat protected void initializeTool(StandAlonePluginTool newTool) { newTool.addExitAction(); + newTool.installUtilityPlugins(); } private Element getDefaultToolElement() { 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/PluginClassManager.java index 7e58d16201..a31596bd54 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/PluginClassManager.java @@ -33,7 +33,6 @@ public class PluginClassManager { public PluginClassManager(Class filterClass, Class exclusionClass) { populatePluginDescriptionMaps(filterClass, exclusionClass); - } public PluginDescription getPluginDescription(String className) { @@ -129,6 +128,7 @@ public class PluginClassManager { PluginPackage pluginPackage = pluginDescription.getPluginPackage(); List list = pluginPackageMap.get(pluginPackage); if (list == null) { + list = new ArrayList<>(); pluginPackageMap.put(pluginPackage, list); } @@ -143,7 +143,7 @@ public class PluginClassManager { * @param classNames the list of classNames from from the old XML file * @return the adjusted class names */ - public List fillInPackageClasses(List classNames) { + public Set fillInPackageClasses(List classNames) { Set packages = new HashSet<>(); Set adjustedClassNames = new HashSet<>(); @@ -169,15 +169,19 @@ public class PluginClassManager { } } - return new ArrayList<>(adjustedClassNames); + return adjustedClassNames; } - public List getPluginClasses(Element element) { - List classNames = new ArrayList<>(); + public Set getPluginClasses(Element element) { + Set classNames = new HashSet<>(); List children = element.getChildren("PACKAGE"); for (Object object : children) { + Element child = (Element) object; + String packageName = child.getAttributeValue("NAME"); + + // classes excluded by name in the xml Set excludedClasses = new HashSet<>(); List grandChildren = child.getChildren("EXCLUDE"); for (Object obj : grandChildren) { @@ -185,6 +189,8 @@ public class PluginClassManager { String excludedClassName = grandChild.getAttributeValue("CLASS"); excludedClasses.add(excludedClassName); } + + // classes included by name in the xml Set includedClasses = new HashSet<>(); grandChildren = child.getChildren("INCLUDE"); for (Object obj : grandChildren) { @@ -193,27 +199,34 @@ public class PluginClassManager { includedClasses.add(excludedClassName); } - String packageName = child.getAttributeValue("NAME"); PluginPackage pluginPackage = PluginPackage.getPluginPackage(packageName); List pluginDescriptionList = packageMap.get(pluginPackage); if (pluginDescriptionList == null) { continue; } + for (PluginDescription pluginDescription : pluginDescriptionList) { - String pluginClass = pluginDescription.getPluginClass().getName(); - if (pluginDescription.getStatus() == PluginStatus.RELEASED) { - if (!excludedClasses.contains(pluginClass)) { - classNames.add(pluginClass); - } - } - else { - if (includedClasses.contains(pluginClass)) { - classNames.add(pluginClass); - } + if (shouldAddPlugin(pluginDescription, includedClasses, excludedClasses)) { + classNames.add(pluginDescription.getPluginClass().getName()); } } } return classNames; + + } + + private boolean shouldAddPlugin(PluginDescription description, Set include, + Set exclude) { + + String className = description.getPluginClass().getName(); + if (include.contains(className)) { + return true; + } + if (exclude.contains(className)) { + return false; + } + return description.getStatus() == PluginStatus.RELEASED; + } public List getPluginPackages() { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginPackage.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginPackage.java index a348d0bb4a..61024f8ceb 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginPackage.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/PluginPackage.java @@ -25,7 +25,8 @@ import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; public abstract class PluginPackage implements ExtensionPoint, Comparable { - public static final int CORE_PRIORITY = 0; + public static final int UTILITY_PRIORITY = 0; + public static final int CORE_PRIORITY = 1; public static final int FEATURE_PRIORITY = 4; public static final int MISCELLANIOUS_PRIORITY = 6; public static final int DEVELOPER_PRIORITY = 8; 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 693d9677d7..42c0f83605 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,7 +28,7 @@ import docking.action.MenuData; import docking.tool.ToolConstants; import docking.widgets.OptionDialog; import ghidra.app.util.FileOpenDropHandler; -import ghidra.framework.main.FrontEndOnly; +import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.model.Project; import ghidra.framework.model.ToolTemplate; import ghidra.framework.options.PreferenceState; @@ -101,7 +101,7 @@ public class GhidraTool extends PluginTool { @Override public PluginClassManager getPluginClassManager() { if (pluginClassManager == null) { - pluginClassManager = new PluginClassManager(Plugin.class, FrontEndOnly.class); + pluginClassManager = new PluginClassManager(Plugin.class, ApplicationLevelOnlyPlugin.class); } return pluginClassManager; } diff --git a/Ghidra/Framework/Project/src/main/resources/images/applications-development-web.png b/Ghidra/Framework/Project/src/main/resources/images/applications-development-web.png new file mode 100644 index 0000000000000000000000000000000000000000..83598180c06c3dc4157df1950395c56ff85e62ad GIT binary patch literal 3254 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 literal 0 HcmV?d00001