From 19a345fdff93e84ecd614278134624e358da05ce Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 10 Jan 2024 14:30:04 -0500 Subject: [PATCH] GP-4188 improve tool associations with LinkHandlers. Removed support for default content-type. --- .../bsim/query/ingest/BSimLaunchable.java | 3 - .../Base/src/main/java/ghidra/GhidraRun.java | 3 - .../app/util/headless/GhidraScriptRunner.java | 3 - .../app/util/headless/HeadlessAnalyzer.java | 3 - .../framework/data/DomainObjectAdapter.java | 90 ++++++---- .../project/tool/ToolServicesImpl.java | 168 +++++------------- 6 files changed, 100 insertions(+), 170 deletions(-) diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java index 5a3594fc75..d0d23ae048 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java @@ -1066,9 +1066,6 @@ public class BSimLaunchable implements GhidraLaunchable { System.err.println("ERROR: " + e.getMessage()); } - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - ApplicationConfiguration config; switch (type) { case 2: diff --git a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java index b5cc4f2c28..c86aacdfee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java @@ -83,9 +83,6 @@ public class GhidraRun implements GhidraLaunchable { ExtensionUtils.initializeExtensions(); - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - updateSplashScreenStatusMessage("Checking for previous project..."); SystemUtilities.runSwingLater(() -> { String projectPath = processArguments(args); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java index c647d85194..4c69a890f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java @@ -159,9 +159,6 @@ public class GhidraScriptRunner implements GhidraLaunchable { */ initializeApplication(applicationLayout, logFile, useLog4j); - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - initializeScriptPaths(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index f13bab0445..b5fa46971b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -206,9 +206,6 @@ public class HeadlessAnalyzer { System.setProperty("java.awt.headless", "true"); System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString()); - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - // Put analyzer in its default state reset(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java index bd75b57952..d8de288618 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java @@ -26,6 +26,7 @@ import ghidra.framework.model.*; import ghidra.framework.store.FileSystem; import ghidra.framework.store.LockException; import ghidra.util.Lock; +import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.datastruct.ListenerSet; @@ -37,13 +38,12 @@ public abstract class DomainObjectAdapter implements DomainObject { protected final static String DEFAULT_NAME = "untitled"; - private static Class defaultDomainObjClass; // Domain object implementation mapped to unknown content type private static HashMap> contentHandlerTypeMap; // maps content-type string to handler private static HashMap, ContentHandler> contentHandlerClassMap; // maps domain object class to handler private static ChangeListener contentHandlerUpdateListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - getContentHandlers(); + initContentHandlerMaps(); } }; @@ -382,33 +382,14 @@ public abstract class DomainObjectAdapter implements DomainObject { } /** - * Set default content type - * - * @param doClass default domain object implementation - */ - public static synchronized void setDefaultContentClass(Class doClass) { - defaultDomainObjClass = doClass; - if (contentHandlerTypeMap != null) { - if (doClass == null) { - contentHandlerTypeMap.remove(null); - } - else { - ContentHandler ch = contentHandlerClassMap.get(doClass); - if (ch != null) { - contentHandlerTypeMap.put(null, ch); - } - } - } - } - - /** - * Get the ContentHandler associated with the specified content-type. + * Get the {@link ContentHandler} associated with the specified content-type. * * @param contentType domain object content type * @return content handler * @throws IOException if no content handler can be found */ - static synchronized ContentHandler getContentHandler(String contentType) throws IOException { + public static synchronized ContentHandler getContentHandler(String contentType) + throws IOException { checkContentHandlerMaps(); ContentHandler ch = contentHandlerTypeMap.get(contentType); if (ch == null) { @@ -418,20 +399,40 @@ public abstract class DomainObjectAdapter implements DomainObject { } /** - * Get the ContentHandler associated with the specified domain object + * Get the {@link ContentHandler} associated with the specified domain object class + * + * @param dobjClass domain object class + * @return content handler + * @throws IOException if no content handler can be found + */ + public static synchronized ContentHandler getContentHandler( + Class dobjClass) throws IOException { + checkContentHandlerMaps(); + ContentHandler ch = contentHandlerClassMap.get(dobjClass); + if (ch == null) { + throw new IOException("Content handler not found for " + dobjClass.getName()); + } + return ch; + } + + /** + * Get the {@link ContentHandler} associated with the specified domain object * * @param dobj domain object * @return content handler * @throws IOException if no content handler can be found */ - public static synchronized ContentHandler getContentHandler(DomainObject dobj) - throws IOException { + public static ContentHandler getContentHandler(DomainObject dobj) throws IOException { + return getContentHandler(dobj.getClass()); + } + + /** + * Get all {@link ContentHandler}s + * @return collection of content handlers + */ + public static Set> getContentHandlers() { checkContentHandlerMaps(); - ContentHandler ch = contentHandlerClassMap.get(dobj.getClass()); - if (ch == null) { - throw new IOException("Content handler not found for " + dobj.getClass().getName()); - } - return ch; + return new HashSet<>(contentHandlerTypeMap.values()); } private static void checkContentHandlerMaps() { @@ -439,23 +440,34 @@ public abstract class DomainObjectAdapter implements DomainObject { return; } - getContentHandlers(); + initContentHandlerMaps(); ClassSearcher.addChangeListener(contentHandlerUpdateListener); } - private synchronized static void getContentHandlers() { - contentHandlerClassMap = new HashMap, ContentHandler>(); - contentHandlerTypeMap = new HashMap>(); + private synchronized static void initContentHandlerMaps() { + HashMap, ContentHandler> classMap = new HashMap<>(); + HashMap> typeMap = new HashMap<>(); @SuppressWarnings("rawtypes") List handlers = ClassSearcher.getInstances(ContentHandler.class); for (ContentHandler ch : handlers) { - contentHandlerTypeMap.put(ch.getContentType(), ch); + String contentType = ch.getContentType(); + if (typeMap.put(contentType, ch) != null) { + Msg.error(DomainObjectAdapter.class, + "Multiple content handlers discovered for content type: " + contentType); + } if (!(ch instanceof LinkHandler)) { - contentHandlerClassMap.put(ch.getDomainObjectClass(), ch); + Class contentClass = ch.getDomainObjectClass(); + if (classMap.put(contentClass, ch) != null) { + Msg.error(DomainObjectAdapter.class, + "Multiple content handlers discovered for content class: " + + contentClass.getSimpleName()); + } } } - setDefaultContentClass(defaultDomainObjClass); + + contentHandlerClassMap = classMap; + contentHandlerTypeMap = typeMap; } @Override diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolServicesImpl.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolServicesImpl.java index 9daffeda75..9e140f8d88 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolServicesImpl.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ToolServicesImpl.java @@ -35,7 +35,6 @@ import ghidra.framework.preferences.Preferences; import ghidra.framework.protocol.ghidra.GetUrlContentTypeTask; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.util.Msg; -import ghidra.util.classfinder.ClassSearcher; import ghidra.util.filechooser.GhidraFileChooserModel; import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.task.TaskLauncher; @@ -53,7 +52,6 @@ class ToolServicesImpl implements ToolServices { private ToolChest toolChest; private ToolManagerImpl toolManager; private ToolChestChangeListener toolChestChangeListener; - private Set> contentHandlers; ToolServicesImpl(ToolChest toolChest, ToolManagerImpl toolManager) { this.toolChest = toolChest; @@ -320,8 +318,11 @@ class ToolServicesImpl implements ToolServices { Set set = new HashSet<>(); // get all known content types - Set> handlers = getContentHandlers(); + Set> handlers = DomainObjectAdapter.getContentHandlers(); for (ContentHandler contentHandler : handlers) { + if (contentHandler instanceof LinkHandler) { + continue; + } set.add(createToolAssociationInfo(contentHandler)); } @@ -351,6 +352,22 @@ class ToolServicesImpl implements ToolServices { @Override public ToolTemplate getDefaultToolTemplate(String contentType) { + + try { + ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(contentType); + if (contentHandler instanceof LinkHandler) { + Class domainObjectClass = + contentHandler.getDomainObjectClass(); + contentHandler = DomainObjectAdapter.getContentHandler(domainObjectClass); + contentType = contentHandler.getContentType(); + } + } + catch (IOException e) { + // Failed to identify content handler + Msg.error(this, e.getMessage()); + return null; + } + String toolName = Preferences.getProperty(getToolAssociationPreferenceKey(contentType), null, true); if (toolName == null) { @@ -379,28 +396,31 @@ class ToolServicesImpl implements ToolServices { } // - // Next, look through for all compatible content handlers find tools for them + // Next, check content handler for its default tool name // - Set> compatibleHandlers = getCompatibleContentHandlers(domainClass); - for (ContentHandler handler : compatibleHandlers) { + try { + ContentHandler handler = DomainObjectAdapter.getContentHandler(domainClass); String defaultToolName = handler.getDefaultToolName(); - if (nameToTemplateMap.get(defaultToolName) != null) { - continue; // already have tool in the map by this name; prefer that tool - } - - ToolTemplate toolChestTemplate = findToolChestToolTemplate(defaultToolName); - if (toolChestTemplate != null) { - // found the tool in the tool chest--use that one - nameToTemplateMap.put(toolChestTemplate.getName(), toolChestTemplate); - continue; - } - - // see if there is a default tool - GhidraToolTemplate defaultToolTemplate = findDefaultToolTemplate(defaultToolName); - if (defaultToolTemplate != null) { - nameToTemplateMap.put(defaultToolTemplate.getName(), defaultToolTemplate); + if (nameToTemplateMap.get(defaultToolName) == null) { + ToolTemplate toolChestTemplate = findToolChestToolTemplate(defaultToolName); + if (toolChestTemplate != null) { + // found the tool in the tool chest--use that one + nameToTemplateMap.put(toolChestTemplate.getName(), toolChestTemplate); + } + else { + // see if there is a default tool + GhidraToolTemplate defaultToolTemplate = + findDefaultToolTemplate(defaultToolName); + if (defaultToolTemplate != null) { + nameToTemplateMap.put(defaultToolTemplate.getName(), defaultToolTemplate); + } + } } } + catch (IOException e) { + // Failed to identify content handler + Msg.error(this, e.getMessage()); + } // // Finally, see if any of the default tools can handle this type and include any that @@ -424,70 +444,23 @@ class ToolServicesImpl implements ToolServices { return new HashSet<>(nameToTemplateMap.values()); } - private Set> getCompatibleContentHandlers( - Class domainClass) { - Set> set = new HashSet<>(); - Set> handlers = getContentHandlers(); - for (ContentHandler contentHandler : handlers) { - Class handlerDomainClass = - contentHandler.getDomainObjectClass(); - if (handlerDomainClass == domainClass) { - set.add(contentHandler); - } - } - return set; - } - private String getToolAssociationPreferenceKey(String contentType) { return TOOL_ASSOCIATION_PREFERENCE + SEPARATOR + contentType; } private String getDefaultToolAssociation(String contentType) { - Set> handlers = getContentHandlers(); - for (ContentHandler contentHandler : handlers) { - String type = contentHandler.getContentType(); - if (type.equals(contentType)) { - return contentHandler.getDefaultToolName(); - } + + try { + ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(contentType); + return contentHandler.getDefaultToolName(); + } + catch (IOException e) { + // Failed to identify content handler + Msg.error(this, e.getMessage()); } return null; } - private Set> getContentHandlers() { - if (contentHandlers != null) { - return contentHandlers; - } - - contentHandlers = new HashSet<>(); - @SuppressWarnings("rawtypes") - List instances = ClassSearcher.getInstances(ContentHandler.class); - for (ContentHandler contentHandler : instances) { - - if (contentHandler instanceof FolderLinkContentHandler) { - continue; // ignore folder link handler - } - - // a bit of validation - String contentType = contentHandler.getContentType(); - if (contentType == null) { - Msg.error(DomainObjectAdapter.class, "ContentHandler " + - contentHandler.getClass().getName() + " does not specify a content type"); - continue; - } - - String toolName = contentHandler.getDefaultToolName(); - if (toolName == null) { - Msg.error(DomainObjectAdapter.class, "ContentHandler " + - contentHandler.getClass().getName() + " does not specify a default tool"); - continue; - } - - contentHandlers.add(contentHandler); - } - - return contentHandlers; - } - private GhidraToolTemplate findToolChestToolTemplate(String toolName) { if (toolName != null) { return (GhidraToolTemplate) toolChest.getToolTemplate(toolName); @@ -509,54 +482,11 @@ class ToolServicesImpl implements ToolServices { return null; } - /** - * Get all running tools that have the same tool chest tool name as this one. - * - * @param tool the tool for comparison. - * - * @return array of tools that are running and named the same as this one. - */ - private PluginTool[] getSameNamedRunningTools(PluginTool tool) { - String toolName = tool.getToolName(); - PluginTool[] tools = toolManager.getRunningTools(); - List toolList = new ArrayList<>(tools.length); - for (PluginTool element : tools) { - if (toolName.equals(element.getToolName())) { - toolList.add(element); - } - } - return toolList.toArray(new PluginTool[toolList.size()]); - } - @Override public PluginTool[] getRunningTools() { return toolManager.getRunningTools(); } - /** - * Search the array of tools for one using the given domainFile. - * - * @param tools array of tools to search - * @param domainFile domain file to find user of - * - * @return first tool found to be using the domainFile - */ - private PluginTool findToolUsingFile(PluginTool[] tools, DomainFile domainFile) { - PluginTool matchingTool = null; - for (int toolNum = 0; (toolNum < tools.length) && (matchingTool == null); toolNum++) { - PluginTool pTool = tools[toolNum]; - // Is this tool the same as the type we are in. - DomainFile[] df = pTool.getDomainFiles(); - for (DomainFile element : df) { - if (domainFile.equals(element)) { - matchingTool = tools[toolNum]; - break; - } - } - } - return matchingTool; - } - @Override public boolean canAutoSave(PluginTool tool) { return toolManager.canAutoSave(tool);