mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 21:26:15 +08:00
Merge remote-tracking branch
'origin/GP-6641-dragonmacher-extensions-preview' (#8984)
This commit is contained in:
@@ -14,8 +14,8 @@
|
||||
into a Ghidra distribution. This allows users to create and share new plugins and scripts.
|
||||
Ghidra ships with some pre-built extensions that not installed by default.
|
||||
</P>
|
||||
<P>Ghidra Extensions can be installed and uninstalled at runtime, with the changes taking effect
|
||||
when Ghidra is restarted. The extension installation dialog can
|
||||
<P>Ghidra Extensions can be installed and uninstalled at runtime, <U>with the changes taking effect
|
||||
when Ghidra is restarted</U>. The extension installation dialog can
|
||||
be opened by selecting the <B>Install Extensions</B> option on the project <B>File</B> menu.</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
@@ -32,11 +32,61 @@
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Installed extensions provide new functionality to Ghidra, such as Plugins, Analyzers and
|
||||
other Extension Points. Non-plugin Extension Points will be automatically discovered and
|
||||
loaded at startup. This is <B>not</B> the case for Plugins. Plugins must be manually
|
||||
enabled inside of the tool that is being used. For example, when running the Code Browser,
|
||||
use the <A href="help/topics/Tool/Configure_Tool.htm">
|
||||
<B>File</B><IMG src="help/shared/arrow.gif" border="0"><B>Congfigure</B></A> menu to
|
||||
enable any plugins added by newly installed extensions.
|
||||
</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="" border="0">The easiest way to find the plugins
|
||||
for a given extension is to use the table view of all known plugins. To see all plugins
|
||||
for an extension:</P>
|
||||
<OL>
|
||||
<LI>
|
||||
Configure plugins via <B>File</B><IMG src="help/shared/arrow.gif" border="0">
|
||||
<B>Congfigure</B>
|
||||
</LI>
|
||||
<LI>
|
||||
Click the <img src="images/plugin.png" /> icon to show all plugins
|
||||
</LI>
|
||||
<LI>
|
||||
If not already visible, add the <B>Module</B> table column via the right-click
|
||||
menu of the table header. (Extensions are modules.)
|
||||
</LI>
|
||||
<LI>
|
||||
Sort on the <B>Module</B> table column
|
||||
</LI>
|
||||
<LI>
|
||||
Optionally, you can type the name of the extension into the filter to hide other modules
|
||||
</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<H2>Dialog Components</H2>
|
||||
|
||||
<H3>Extensions Table</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<P>To install an extension, select the extension's checkbox. To unininstall, deselect the
|
||||
checkbox.</P>
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/note.png" alt="" border="0">If the checkbox is not editable,
|
||||
that means that means the extension is installed and canont be uninistalled. This can
|
||||
happen in development mode with extensions that live in source control.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<P>The list of extensions is populated when the dialog is launched. To build the list, Ghidra
|
||||
looks in several locations:</P>
|
||||
|
||||
|
||||
+2
-2
@@ -371,8 +371,8 @@
|
||||
<P class="relatedtopic">Related Topics</P>
|
||||
|
||||
<UL>
|
||||
<LI><A href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Key
|
||||
Bindings</A></LI>
|
||||
<LI><A href="../BundleManager/BundleManager.htm">Ghidra Bundles</A>
|
||||
<LI><A href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Key Bindings</A></LI>
|
||||
</UL>
|
||||
|
||||
<P> </P>
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
<UL>
|
||||
<LI>
|
||||
<B>Version</B> - Ghidra, Operating System, and Java version information. Clicking the
|
||||
<B><I>Copy</I></B> button will copy this information to the clipboard for easy transfer into a bug report.
|
||||
<B><I>Copy</I></B> button will copy this information to the clipboard for easy transfer into
|
||||
a bug report.
|
||||
</LI>
|
||||
<LI>
|
||||
<B>Memory</B> - JVM memory usage. Clicking the <B><I>Collect Garbage</I></B> button will
|
||||
@@ -47,7 +48,11 @@
|
||||
<B>Modules</B> - A list of discovered Ghidra Modules.
|
||||
</LI>
|
||||
<LI>
|
||||
<B>Extension Points</B> - A list of discovered Ghidra Extension Points.
|
||||
<B>Extension Points</B> - A list of discovered Ghidra Extension Points.<BR><BR>
|
||||
Note: Extension Points differ from Extensions. Extension Points are classes that Ghidra
|
||||
will find at startup. Extensions are additional Ghidra Modules that can be installed
|
||||
by the user. Further, Extensions may contain Extension Points.
|
||||
<BR><BR>
|
||||
</LI>
|
||||
<LI>
|
||||
<B>Classpath</B> - The ordered classpath for the active Ghidra application.
|
||||
|
||||
+10
-2
@@ -24,6 +24,7 @@ import java.util.*;
|
||||
import org.junit.AfterClass;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraTestApplicationLayout;
|
||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||
@@ -659,9 +660,16 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
|
||||
Set<ClassFileInfo> serviceSet = extensionPointSuffixToInfoMap.get(suffix);
|
||||
assertNotNull(serviceSet);
|
||||
serviceSet.clear();
|
||||
ClassFileInfo info = new ClassFileInfo("", replacement.getClass().getName(), suffix);
|
||||
Class<? extends Object> clazz = replacement.getClass();
|
||||
ResourceFile module = Application.getModuleContainingClass(clazz);
|
||||
String modulePath = "";
|
||||
if (module != null) {
|
||||
modulePath = module.getAbsolutePath();
|
||||
}
|
||||
String name = clazz.getName();
|
||||
ClassFileInfo info = new ClassFileInfo("", name, suffix, modulePath);
|
||||
serviceSet.add(info);
|
||||
loadedCache.put(info, replacement.getClass());
|
||||
loadedCache.put(info, clazz);
|
||||
}
|
||||
|
||||
T instance = tool.getService(service);
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -263,7 +263,7 @@ public class AnalysisOptions2Test extends AbstractGhidraHeadedIntegrationTest {
|
||||
assertNotNull(analyzerSet);
|
||||
|
||||
analyzerSet.removeIf(c -> c.name().contains("TestStubAnalyzer"));
|
||||
ClassFileInfo info = new ClassFileInfo("", analyzer.getName(), "Analyzer");
|
||||
ClassFileInfo info = new ClassFileInfo("", analyzer.getName(), "Analyzer", "");
|
||||
analyzerSet.add(info);
|
||||
loadedCache.put(info, analyzer);
|
||||
}
|
||||
|
||||
+22
-8
@@ -31,13 +31,14 @@ import ghidra.framework.Application;
|
||||
import ghidra.framework.project.tool.testplugins.TestExtensionHello2Plugin;
|
||||
import ghidra.framework.project.tool.testplugins.TestExtensionHelloPlugin;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.extensions.ExtensionUtils;
|
||||
import ghidra.util.xml.GenericXMLOutputter;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
import utility.application.ApplicationLayout;
|
||||
|
||||
public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public class ToolExtensionStatusManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private ApplicationLayout appLayout;
|
||||
private FakeToolExtensionsEnabledState extensionsState;
|
||||
@@ -130,6 +131,7 @@ public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
// verify user is prompted to add new plugins
|
||||
extensionManager.checkForNewExtensions();
|
||||
waitForSwing();
|
||||
waitForSwing();
|
||||
assertTrue(extensionsState.didPrompt());
|
||||
}
|
||||
|
||||
@@ -274,35 +276,47 @@ public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private class FakeToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
|
||||
private Map<String, Set<Class<?>>> extensions = new HashMap<>();
|
||||
private Set<Class<?>> installedPlugins = new HashSet<>();
|
||||
private Map<String, Set<ClassFileInfo>> extensions = new HashMap<>();
|
||||
private Set<ClassFileInfo> installedPlugins = new HashSet<>();
|
||||
private boolean didPrompt = false;
|
||||
|
||||
@Override
|
||||
public Map<String, Set<Class<?>>> getAllKnownExtensions() {
|
||||
public Map<String, Set<ClassFileInfo>> getAllKnownExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeInstalledPlugins(Set<Class<?>> plugins) {
|
||||
public void removeInstalledPlugins(Set<ClassFileInfo> plugins) {
|
||||
plugins.removeAll(installedPlugins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propmtToConfigureNewPlugins(Set<Class<?>> plugins) {
|
||||
public void propmtToConfigureNewPlugins(Set<ClassFileInfo> plugins) {
|
||||
didPrompt = true;
|
||||
}
|
||||
|
||||
void addExtension(String name, Class<?>... classes) {
|
||||
void addExtension(String name, ClassFileInfo... classes) {
|
||||
extensions.put(name, Set.of(classes));
|
||||
}
|
||||
|
||||
void addExtension(String name, Class<?>... classes) {
|
||||
|
||||
for (Class<?> c : classes) {
|
||||
String className = c.getName();
|
||||
ClassFileInfo info = new ClassFileInfo("/fake/path", className, "Plugin", "");
|
||||
addExtension(name, info);
|
||||
}
|
||||
}
|
||||
|
||||
boolean didPrompt() {
|
||||
return didPrompt;
|
||||
}
|
||||
|
||||
void setInstalled(Class<TestExtensionHelloPlugin> c) {
|
||||
installedPlugins.add(c);
|
||||
|
||||
String name = c.getName();
|
||||
ClassFileInfo info = new ClassFileInfo("/fake/path", name, "Plugin", "");
|
||||
installedPlugins.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -34,8 +34,10 @@ import docking.dnd.GClipboard;
|
||||
import docking.dnd.StringTransferable;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.WebColors;
|
||||
import ghidra.util.layout.HorizontalLayout;
|
||||
@@ -351,12 +353,20 @@ public class SettableColorSwatchChooserPanel extends AbstractColorChooserPanel {
|
||||
|
||||
String text = colorNameField.getText();
|
||||
String colorText = text.replaceAll("\s", "");
|
||||
|
||||
Color color = WebColors.getColor(colorText);
|
||||
|
||||
if (color == null) {
|
||||
color = WebColors.getColor('#' + colorText);
|
||||
}
|
||||
|
||||
if (color == null) {
|
||||
GColor gColor = Palette.getColor(colorText);
|
||||
if (!gColor.isUnresolved()) {
|
||||
color = gColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (color != null) {
|
||||
getColorSelectionModel().setSelectedColor(color);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -18,31 +18,40 @@ package ghidra.util.classfinder;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
class ClassDir {
|
||||
|
||||
private String dirPath;
|
||||
private File dir;
|
||||
private String modulePath = "";
|
||||
private ClassPackage classPackage;
|
||||
|
||||
ClassDir(String dirPath, TaskMonitor monitor) throws CancelledException {
|
||||
this.dirPath = dirPath;
|
||||
this.dir = new File(dirPath);
|
||||
classPackage = new ClassPackage(dir, "", monitor);
|
||||
ClassDir(File dir, TaskMonitor monitor) throws CancelledException {
|
||||
this.dir = dir;
|
||||
ResourceFile module = Application.getModuleContainingResourceFile(new ResourceFile(dir));
|
||||
if (module != null) {
|
||||
modulePath = module.getAbsolutePath();
|
||||
}
|
||||
classPackage = new ClassPackage(this, "", monitor);
|
||||
}
|
||||
|
||||
void getClasses(List<ClassFileInfo> list, TaskMonitor monitor) throws CancelledException {
|
||||
classPackage.getClasses(list, monitor);
|
||||
}
|
||||
|
||||
String getDirPath() {
|
||||
return dirPath;
|
||||
File getDir() {
|
||||
return dir;
|
||||
}
|
||||
|
||||
String getModulePath() {
|
||||
return modulePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dirPath;
|
||||
return dir.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -21,5 +21,18 @@ package ghidra.util.classfinder;
|
||||
* @param path The path to the class file (or jar containing the class)
|
||||
* @param name The name of the class (including package)
|
||||
* @param suffix The class suffix (i.e., extension point type name)
|
||||
* @param module The module path for this class
|
||||
*/
|
||||
public record ClassFileInfo(String path, String name, String suffix) {}
|
||||
public record ClassFileInfo(String path, String name, String suffix, String module) {
|
||||
|
||||
/**
|
||||
* {@return the simple class name (no package name) for the class represented by this info}
|
||||
*/
|
||||
public String simpleName() {
|
||||
int index = name.lastIndexOf('.');
|
||||
if (index < 0) {
|
||||
return name; // no package
|
||||
}
|
||||
return name.substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,15 @@ class ClassJar implements ClassLocation {
|
||||
private static final Set<String> USER_PLUGIN_PATHS = loadUserPluginPaths();
|
||||
|
||||
private Set<ClassFileInfo> classes = new HashSet<>();
|
||||
private String path;
|
||||
|
||||
ClassJar(String path, TaskMonitor monitor) throws CancelledException {
|
||||
this.path = path;
|
||||
loadUserPluginPaths();
|
||||
private File file;
|
||||
private String modulePath = "";
|
||||
|
||||
ClassJar(File file, TaskMonitor monitor) throws CancelledException {
|
||||
this.file = file;
|
||||
ResourceFile module = Application.getModuleContainingResourceFile(new ResourceFile(file));
|
||||
if (module != null) {
|
||||
modulePath = module.getAbsolutePath();
|
||||
}
|
||||
scanJar(monitor);
|
||||
}
|
||||
|
||||
@@ -68,8 +71,6 @@ class ClassJar implements ClassLocation {
|
||||
|
||||
private void scanJar(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
File file = new File(path);
|
||||
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
|
||||
String pathName = jarFile.getName();
|
||||
@@ -84,7 +85,7 @@ class ClassJar implements ClassLocation {
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Error reading jarFile: " + path, e);
|
||||
Msg.error(this, "Error reading jarFile: " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,13 +177,14 @@ class ClassJar implements ClassLocation {
|
||||
|
||||
String epName = ClassSearcher.getExtensionPointSuffix(name);
|
||||
if (epName != null) {
|
||||
classes.add(new ClassFileInfo(path, name, epName));
|
||||
String path = file.getAbsolutePath();
|
||||
classes.add(new ClassFileInfo(path, name, epName, modulePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path;
|
||||
return file.toString();
|
||||
}
|
||||
|
||||
private static String getPatchDirPath() {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -29,28 +29,33 @@ class ClassPackage implements ClassLocation {
|
||||
pathname -> pathname.getName().endsWith(CLASS_EXT);
|
||||
|
||||
private Set<ClassPackage> children = new HashSet<>();
|
||||
private File rootDir;
|
||||
private ClassDir classDir;
|
||||
private File packageDir;
|
||||
private String packageName;
|
||||
private Set<ClassFileInfo> classes = new HashSet<>();
|
||||
|
||||
ClassPackage(File rootDir, String packageName, TaskMonitor monitor) throws CancelledException {
|
||||
ClassPackage(ClassDir classDir, String packageName, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
monitor.checkCancelled();
|
||||
this.rootDir = rootDir;
|
||||
|
||||
this.classDir = classDir;
|
||||
|
||||
File rootDir = classDir.getDir();
|
||||
this.packageName = packageName;
|
||||
this.packageDir = getPackageDir(rootDir, packageName);
|
||||
scanClasses();
|
||||
this.packageDir = new File(rootDir, packageName.replace('.', File.separatorChar));
|
||||
scanClasses(rootDir);
|
||||
scanSubPackages(monitor);
|
||||
}
|
||||
|
||||
private void scanClasses() {
|
||||
private void scanClasses(File rootDir) {
|
||||
|
||||
String path = rootDir.getAbsolutePath();
|
||||
Set<String> allClassNames = getAllClassNames();
|
||||
for (String className : allClassNames) {
|
||||
String epName = ClassSearcher.getExtensionPointSuffix(className);
|
||||
if (epName != null) {
|
||||
classes.add(new ClassFileInfo(path, className, epName));
|
||||
String module = classDir.getModulePath();
|
||||
classes.add(new ClassFileInfo(path, className, epName, module));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,24 +85,18 @@ class ClassPackage implements ClassLocation {
|
||||
}
|
||||
|
||||
monitor.setMessage("Scanning package: " + pkg);
|
||||
children.add(new ClassPackage(rootDir, pkg, monitor));
|
||||
children.add(new ClassPackage(classDir, pkg, monitor));
|
||||
}
|
||||
}
|
||||
|
||||
private File getPackageDir(File lRootDir, String lPackageName) {
|
||||
return new File(lRootDir, lPackageName.replace('.', File.separatorChar));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getClasses(List<ClassFileInfo> list, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
list.addAll(classes);
|
||||
|
||||
Iterator<ClassPackage> it = children.iterator();
|
||||
while (it.hasNext()) {
|
||||
for (ClassPackage subPkg : children) {
|
||||
monitor.checkCancelled();
|
||||
ClassPackage subPkg = it.next();
|
||||
subPkg.getClasses(list, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +168,10 @@ public class ClassSearcher {
|
||||
"Cannot call the getClasses() while the ClassSearcher is searching!");
|
||||
}
|
||||
|
||||
String suffix = getExtensionPointSuffix(ancestorClass.getName());
|
||||
String className = ancestorClass.getName();
|
||||
String suffix = getExtensionPointSuffix(className);
|
||||
if (suffix == null) {
|
||||
Msg.error(ClassSearcher.class, "Class is not a known extension point: " + className);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -414,8 +416,11 @@ public class ClassSearcher {
|
||||
for (String searchPath : gatherSearchPaths()) {
|
||||
String lcSearchPath = searchPath.toLowerCase();
|
||||
File searchFile = new File(searchPath);
|
||||
if ((lcSearchPath.endsWith(".jar") || lcSearchPath.endsWith(".zip")) &&
|
||||
searchFile.exists()) {
|
||||
if (!searchFile.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lcSearchPath.endsWith(".jar") || lcSearchPath.endsWith(".zip")) {
|
||||
|
||||
if (ClassJar.ignoreJar(searchPath)) {
|
||||
log.trace("Ignoring jar file: {}", searchPath);
|
||||
@@ -423,11 +428,11 @@ public class ClassSearcher {
|
||||
}
|
||||
|
||||
log.trace("Searching jar file: {}", searchPath);
|
||||
classJars.add(new ClassJar(searchPath, monitor));
|
||||
classJars.add(new ClassJar(searchFile, monitor));
|
||||
}
|
||||
else if (searchFile.isDirectory()) {
|
||||
log.trace("Searching classpath directory: {}", searchPath);
|
||||
classDirs.add(new ClassDir(searchPath, monitor));
|
||||
classDirs.add(new ClassDir(searchFile, monitor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,8 +538,8 @@ public class ClassSearcher {
|
||||
for (String className : classNames) {
|
||||
String epName = getExtensionPointSuffix(className);
|
||||
if (epName != null) {
|
||||
extensionClasses
|
||||
.add(new ClassFileInfo(appRoot.getAbsolutePath(), className, epName));
|
||||
String path = appRoot.getAbsolutePath();
|
||||
extensionClasses.add(new ClassFileInfo(path, className, epName, ""));
|
||||
}
|
||||
}
|
||||
return extensionClasses.stream()
|
||||
|
||||
@@ -120,6 +120,9 @@ public class ExtensionUtils {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return all installed extensions that are not marked for uninstall}
|
||||
*/
|
||||
public static Set<ExtensionDetails> getActiveInstalledExtensions() {
|
||||
return getAllInstalledExtensions().getActiveExtensions();
|
||||
}
|
||||
|
||||
@@ -291,8 +291,8 @@ public class ApplicationThemeManager extends ThemeManager {
|
||||
update(() -> {
|
||||
updateChangedValuesMap(currentValue, newValue);
|
||||
currentValues.addColor(newValue);
|
||||
notifyThemeChanged(new ColorChangedThemeEvent(currentValues, newValue));
|
||||
lookAndFeelManager.colorsChanged();
|
||||
notifyThemeChanged(new ColorChangedThemeEvent(currentValues, newValue));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -135,7 +135,7 @@ public class GColor extends Color {
|
||||
* @return true if this GColor could not find a value for its color id in the current theme
|
||||
*/
|
||||
public boolean isUnresolved() {
|
||||
return delegate == ColorValue.LAST_RESORT_DEFAULT;
|
||||
return delegate == ColorValue.LAST_RESORT_DEFAULT || delegate == ThemeManager.DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -135,6 +135,8 @@ public class GThemeDefaults {
|
||||
*/
|
||||
public static class Palette {
|
||||
|
||||
private static final String PALETTE_PREFIX = "color.palette.";
|
||||
|
||||
/** Transparent color */
|
||||
public static final Color NO_COLOR = getColor("nocolor");
|
||||
|
||||
@@ -174,7 +176,10 @@ public class GThemeDefaults {
|
||||
* @return the GColor
|
||||
*/
|
||||
public static GColor getColor(String name) {
|
||||
return new GColor("color.palette." + name);
|
||||
if (name.startsWith(PALETTE_PREFIX)) {
|
||||
return new GColor(name);
|
||||
}
|
||||
return new GColor(PALETTE_PREFIX + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ color.fg.extensionpanel.details.name = color.palette.limegreen
|
||||
color.fg.extensionpanel.path = color.palette.blue
|
||||
color.fg.extensionpanel.details.title = color.palette.maroon
|
||||
color.fg.extensionpanel.details.version = color.palette.blue
|
||||
color.fg.extensionpanel.details.classes.header = color.palette.maroon
|
||||
color.fg.extensionpanel.details.classes.type = color.palette.darkkhaki
|
||||
|
||||
color.fg.options.keybindings.table.unregistered = color.palette.lightgray
|
||||
color.fg.options.keybindings.table.unregistered.selected.unfocused = color.palette.gray
|
||||
|
||||
color.fg.pluginpanel.name = color.fg
|
||||
color.fg.pluginpanel.description = color.palette.gray
|
||||
color.fg.plugin.package.panel.name = color.fg
|
||||
color.fg.plugin.package.panel.description = color.palette.gray
|
||||
|
||||
color.bg.panel.details = color.bg
|
||||
color.fg.pluginpanel.details.title = color.palette.maroon
|
||||
@@ -90,9 +92,10 @@ font.task.viewer = sansserif-bold-36
|
||||
font.task.progress.label.message = sansserif-plain-12
|
||||
font.user.agreement = sansserif-italic-22
|
||||
|
||||
font.panel.details = font.standard
|
||||
font.panel.details.monospaced = font.monospaced[bold]
|
||||
font.pluginpanel.name = sansserif-plain-18
|
||||
font.plugin.details.panel.default = font.standard
|
||||
font.plugin.details.panel.monospaced = font.monospaced[bold]
|
||||
|
||||
font.plugin.package.panel.name = sansserif-plain-18
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ import ghidra.framework.plugintool.dialog.ManagePluginsDialog;
|
||||
import ghidra.framework.plugintool.mgr.*;
|
||||
import ghidra.framework.plugintool.util.*;
|
||||
import ghidra.framework.project.ProjectDataService;
|
||||
import ghidra.framework.project.extensions.ExtensionTableProvider;
|
||||
import ghidra.framework.project.extensions.ExtensionTableDialog;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
@@ -339,7 +339,7 @@ public abstract class PluginTool extends AbstractDockingTool {
|
||||
* Displays the extensions installation dialog.
|
||||
*/
|
||||
public void showExtensions() {
|
||||
showDialog(new ExtensionTableProvider(this));
|
||||
showDialog(new ExtensionTableDialog(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+46
-20
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -17,10 +17,10 @@ package ghidra.framework.plugintool.dialog;
|
||||
|
||||
import static ghidra.util.HTMLUtilities.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import docking.widgets.label.GDHtmlLabel;
|
||||
import generic.theme.*;
|
||||
@@ -30,12 +30,13 @@ import generic.theme.*;
|
||||
*/
|
||||
public abstract class AbstractDetailsPanel extends JPanel {
|
||||
|
||||
protected static final String FONT_DEFAULT = "font.panel.details";
|
||||
protected static final String FONT_MONOSPACED = "font.panel.details.monospaced";
|
||||
protected static final String FONT_DEFAULT = "font.plugin.details.panel.default";
|
||||
protected static final String FONT_MONOSPACED = "font.plugin.details.panel.monospaced";
|
||||
|
||||
// based on the default font size
|
||||
private static final int PREFERRED_WIDTH = 900;
|
||||
private static final int LEFT_COLUMN_WIDTH = 150;
|
||||
private static final int MIN_WIDTH = 700;
|
||||
protected static final int LEFT_COLUMN_WIDTH = 150;
|
||||
protected static final int RIGHT_MARGIN = 30;
|
||||
|
||||
// Font attributes for the title of each row.
|
||||
protected static GAttributes titleAttrs;
|
||||
@@ -44,10 +45,7 @@ public abstract class AbstractDetailsPanel extends JPanel {
|
||||
protected JScrollPane sp;
|
||||
|
||||
private ThemeListener themeListener = e -> {
|
||||
|
||||
if (e.isFontChanged(FONT_DEFAULT) || e.isFontChanged(FONT_MONOSPACED)) {
|
||||
updateFieldAttributes();
|
||||
}
|
||||
updateFieldAttributes();
|
||||
};
|
||||
|
||||
protected AbstractDetailsPanel() {
|
||||
@@ -81,15 +79,39 @@ public abstract class AbstractDetailsPanel extends JPanel {
|
||||
*/
|
||||
protected void createMainPanel() {
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
textLabel = new GDHtmlLabel() {
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
|
||||
// overridden to force word-wrapping by limiting the preferred size of the label
|
||||
Dimension mySize = super.getPreferredSize();
|
||||
int rightColumnWidth = AbstractDetailsPanel.this.getWidth() - LEFT_COLUMN_WIDTH;
|
||||
mySize.width = Math.max(MIN_WIDTH, rightColumnWidth);
|
||||
return mySize;
|
||||
// Overridden to force word-wrapping by limiting the preferred size of the label.
|
||||
// Specifically, long descriptions will get word-wrapped by the html document when
|
||||
// the text is longer than the preferred width.
|
||||
Dimension size = super.getPreferredSize();
|
||||
int availableRightWidth = AbstractDetailsPanel.this.getWidth() - LEFT_COLUMN_WIDTH;
|
||||
size.width = Math.max(MIN_WIDTH, availableRightWidth);
|
||||
|
||||
View v = (View) getClientProperty("html");
|
||||
if (v == null) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// We may have html wider than the chosen width that cannot wrap due to not having
|
||||
// any whitespace (e.g., a file path). In that case, the display will run past the
|
||||
// right edge of the screen without showing a horizontal scrollbar. We can force
|
||||
// the scroll bar to appear by making the chosen width be the html minimum width
|
||||
// (which is the width needed to render an unbreakable line of text).
|
||||
Insets i = getInsets();
|
||||
int availableWidth = size.width - (i.left + i.right);
|
||||
int htmlw = (int) v.getMinimumSpan(View.X_AXIS);
|
||||
boolean isClipped = htmlw > availableWidth;
|
||||
if (isClipped) {
|
||||
// the minimum html is wider than the current preferred width
|
||||
size.width = htmlw;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,7 +120,7 @@ public abstract class AbstractDetailsPanel extends JPanel {
|
||||
textLabel.setBackground(new GColor("color.bg.panel.details"));
|
||||
sp = new JScrollPane(textLabel);
|
||||
sp.getVerticalScrollBar().setUnitIncrement(10);
|
||||
sp.setPreferredSize(new Dimension(MIN_WIDTH, 200));
|
||||
sp.setPreferredSize(new Dimension(PREFERRED_WIDTH, 300));
|
||||
add(sp, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -110,9 +132,13 @@ public abstract class AbstractDetailsPanel extends JPanel {
|
||||
* @param rowName the name of the row to add
|
||||
*/
|
||||
protected void insertRowTitle(StringBuilder buffer, String rowName) {
|
||||
insertRowTitle(buffer, rowName, titleAttrs);
|
||||
}
|
||||
|
||||
protected void insertRowTitle(StringBuilder buffer, String rowName, GAttributes attributes) {
|
||||
buffer.append("<TR>");
|
||||
buffer.append("<TD VALIGN=\"TOP\">");
|
||||
insertHTMLLine(buffer, rowName + ":", titleAttrs);
|
||||
buffer.append("<TD VALIGN=\"TOP\" NOWRAP>");
|
||||
insertHTMLLine(buffer, rowName + ":", attributes);
|
||||
buffer.append("</TD>");
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -111,6 +111,15 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
|
||||
insertRowTitle(buffer, "Category");
|
||||
insertRowValue(buffer, descriptor.getCategory(), categoriesAttrs);
|
||||
|
||||
if (descriptor.isInExtension()) {
|
||||
insertRowTitle(buffer, "Extension");
|
||||
}
|
||||
else {
|
||||
insertRowTitle(buffer, "Module");
|
||||
}
|
||||
String module = descriptor.getModuleName();
|
||||
insertRowValue(buffer, module, categoriesAttrs);
|
||||
|
||||
insertRowTitle(buffer, "Plugin Class");
|
||||
insertRowValue(buffer, descriptor.getPluginClass().getName(), classAttrs);
|
||||
|
||||
|
||||
+3
-3
@@ -175,8 +175,8 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
|
||||
labelPanel.setBackground(BG);
|
||||
|
||||
GLabel nameLabel = new GLabel(pluginPackage.getName());
|
||||
Gui.registerFont(nameLabel, "font.pluginpanel.name");
|
||||
nameLabel.setForeground(new GColor("color.fg.pluginpanel.name"));
|
||||
Gui.registerFont(nameLabel, "font.plugin.package.panel.name");
|
||||
nameLabel.setForeground(new GColor("color.fg.plugin.package.panel.name"));
|
||||
labelPanel.add(nameLabel);
|
||||
|
||||
GHyperlinkComponent configureHyperlink = createConfigureHyperlink();
|
||||
@@ -206,7 +206,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
|
||||
String htmlDescription = enchanceDescription(pluginPackage.getDescription());
|
||||
|
||||
JLabel descriptionlabel = new GHtmlLabel(htmlDescription);
|
||||
descriptionlabel.setForeground(new GColor("color.fg.pluginpanel.description"));
|
||||
descriptionlabel.setForeground(new GColor("color.fg.plugin.package.panel.description"));
|
||||
descriptionlabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
|
||||
descriptionlabel.setVerticalAlignment(SwingConstants.TOP);
|
||||
descriptionlabel.setToolTipText(
|
||||
|
||||
+16
-3
@@ -25,6 +25,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.util.Msg;
|
||||
import utilities.util.FileUtilities;
|
||||
import utility.application.ApplicationLayout;
|
||||
|
||||
/**
|
||||
* Class to hold meta information about a plugin, derived from meta-data attached to
|
||||
@@ -124,11 +126,11 @@ public class PluginDescription implements Comparable<PluginDescription> {
|
||||
if (path.startsWith(fileProtoPrefix)) {
|
||||
path = path.substring(fileProtoPrefix.length() + 1);
|
||||
}
|
||||
return path;
|
||||
return '/' + path;
|
||||
}
|
||||
String classpath = pluginClass.getName();
|
||||
path = path.substring(0, path.length() - classpath.length() - DOTCLASS_EXT.length() - 1);
|
||||
return path;
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +151,7 @@ public class PluginDescription implements Comparable<PluginDescription> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the module that contains the plugin.
|
||||
* Returns the name of the module that contains the plugin.
|
||||
* @return the module name
|
||||
*/
|
||||
public String getModuleName() {
|
||||
@@ -161,6 +163,17 @@ public class PluginDescription implements Comparable<PluginDescription> {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if this plugin is provided by an extension}
|
||||
*/
|
||||
public boolean isInExtension() {
|
||||
String myPath = getSourceLocation();
|
||||
ResourceFile myLocation = new ResourceFile(myPath);
|
||||
ApplicationLayout layout = Application.getApplicationLayout();
|
||||
List<ResourceFile> extDirs = layout.getExtensionInstallationDirs();
|
||||
return FileUtilities.isPathContainedWithin(extDirs, myLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class of the plugin.
|
||||
* @return plugin class object
|
||||
|
||||
+103
-12
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -17,10 +17,17 @@ package ghidra.framework.project.extensions;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.Point;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JViewport;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||
import generic.theme.*;
|
||||
import ghidra.framework.plugintool.dialog.AbstractDetailsPanel;
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.extensions.ExtensionDetails;
|
||||
|
||||
/**
|
||||
@@ -43,6 +50,11 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
|
||||
private static final GColor FG_COLOR_VERSION =
|
||||
new GColor("color.fg.extensionpanel.details.version");
|
||||
|
||||
private static final GColor FG_COLOR_CLASSES_HEADER =
|
||||
new GColor("color.fg.extensionpanel.details.classes.header");
|
||||
private static final GColor FG_COLOR_CLASSES_TYPE =
|
||||
new GColor("color.fg.extensionpanel.details.classes.type");
|
||||
|
||||
/** Attribute sets define the visual characteristics for each field */
|
||||
private GAttributes nameAttrSet;
|
||||
private GAttributes descrAttrSet;
|
||||
@@ -50,7 +62,11 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
|
||||
private GAttributes createdOnAttrSet;
|
||||
private GAttributes versionAttrSet;
|
||||
private GAttributes pathAttrSet;
|
||||
private ExtensionDetails currentDetails;
|
||||
|
||||
private GAttributes classesHeaderAttrSet;
|
||||
private GAttributes classesTypeAtrrSet;
|
||||
|
||||
private ExtensionRowObject currentRowObject;
|
||||
|
||||
ExtensionDetailsPanel(ExtensionTablePanel tablePanel) {
|
||||
createFieldAttributes();
|
||||
@@ -83,23 +99,30 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
|
||||
|
||||
@Override
|
||||
protected void refresh() {
|
||||
setDescription(currentDetails);
|
||||
|
||||
JViewport vp = sp.getViewport();
|
||||
Point p = vp.getViewPosition();
|
||||
|
||||
setDescription(currentRowObject);
|
||||
|
||||
// restore the viewer's scrolled position to avoid jumping around
|
||||
vp.setViewPosition(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this panel with the given extension.
|
||||
*
|
||||
* @param details the extension to display
|
||||
*/
|
||||
public void setDescription(ExtensionDetails details) {
|
||||
private void setDescription(ExtensionRowObject ro) {
|
||||
|
||||
this.currentDetails = details;
|
||||
this.currentRowObject = ro;
|
||||
clear();
|
||||
if (details == null) {
|
||||
if (ro == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExtensionDetails details = ro.getExtension();
|
||||
|
||||
StringBuilder buffer = new StringBuilder("<html>");
|
||||
|
||||
buffer.append("<H2>Extension Properties</H2>");
|
||||
|
||||
buffer.append("<TABLE cellpadding=2>");
|
||||
|
||||
insertRowTitle(buffer, "Name");
|
||||
@@ -133,10 +156,75 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
|
||||
|
||||
buffer.append("</TABLE>");
|
||||
|
||||
addExtensionClasses(buffer, ro);
|
||||
|
||||
textLabel.setText(buffer.toString());
|
||||
sp.getViewport().setViewPosition(new Point(0, 0));
|
||||
}
|
||||
|
||||
private void addExtensionClasses(StringBuilder buffer, ExtensionRowObject ro) {
|
||||
|
||||
Set<ClassFileInfo> infos = ro.getClassInfos();
|
||||
if (infos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.append("<BR><CENTER><HR></CENTER><BR>");
|
||||
|
||||
buffer.append("<H2>Provided Extension Points</H2>");
|
||||
|
||||
buffer.append("<TABLE cellpadding=4>");
|
||||
|
||||
buffer.append("<TR>");
|
||||
insertHeader(buffer, "Type");
|
||||
insertHeader(buffer, "Implementations");
|
||||
buffer.append("</TR>");
|
||||
|
||||
Map<String, Set<ClassFileInfo>> classesByType = infos.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
ClassFileInfo::suffix,
|
||||
Collectors.toSet()));
|
||||
|
||||
Set<Entry<String, Set<ClassFileInfo>>> entries = classesByType.entrySet();
|
||||
for (Entry<String, Set<ClassFileInfo>> entry : entries) {
|
||||
String type = entry.getKey();
|
||||
|
||||
insertRowTitle(buffer, type, classesTypeAtrrSet);
|
||||
|
||||
StringBuilder infosBuffer = new StringBuilder();
|
||||
Set<ClassFileInfo> typeInfos = entry.getValue();
|
||||
for (ClassFileInfo typeInfo : typeInfos) {
|
||||
String name = typeInfo.name();
|
||||
String shortName = getShortName(name);
|
||||
insertHTMLLine(infosBuffer, shortName, descrAttrSet);
|
||||
}
|
||||
|
||||
buffer.append("<TD VALIGN=\"TOP\" WIDTH=\"80%\">");
|
||||
buffer.append(infosBuffer.toString());
|
||||
buffer.append("</TD>");
|
||||
buffer.append("</TR>");
|
||||
}
|
||||
|
||||
buffer.append("</TABLE>");
|
||||
|
||||
}
|
||||
|
||||
private String getShortName(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
if (index < 0) {
|
||||
return name; // no package
|
||||
}
|
||||
return name.substring(index + 1);
|
||||
}
|
||||
|
||||
protected void insertHeader(StringBuilder buffer, String rowName) {
|
||||
|
||||
buffer.append("<TH VALIGN=\"TOP\" ALIGN=\"LEFT\">");
|
||||
insertHTMLLine(buffer, rowName, classesHeaderAttrSet);
|
||||
buffer.append("</TH>");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createFieldAttributes() {
|
||||
|
||||
@@ -148,5 +236,8 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
|
||||
createdOnAttrSet = new GAttributes(font, FG_COLOR_DATE);
|
||||
versionAttrSet = new GAttributes(font, FG_COLOR_VERSION);
|
||||
pathAttrSet = new GAttributes(font, FG_COLOR_PATH);
|
||||
classesHeaderAttrSet = new GAttributes(font, FG_COLOR_CLASSES_HEADER);
|
||||
classesTypeAtrrSet = new GAttributes(font, FG_COLOR_CLASSES_TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/* ###
|
||||
* 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.extensions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.extensions.ExtensionDetails;
|
||||
import ghidra.util.extensions.ExtensionUtils;
|
||||
|
||||
/**
|
||||
* A class that contains an {@link ExtensionDetails} and any {@link ClassFileInfo extension points}
|
||||
* loaded from that extension.
|
||||
*/
|
||||
public class ExtensionInstallationInfo {
|
||||
|
||||
private ExtensionDetails extension;
|
||||
private Set<ClassFileInfo> classInfos = new HashSet<>();
|
||||
|
||||
/**
|
||||
* {@return information for each installed extension}
|
||||
*/
|
||||
public static Set<ExtensionInstallationInfo> get() {
|
||||
Set<ExtensionDetails> extensions = ExtensionUtils.getInstalledExtensions();
|
||||
return loadExtensionPointInfo(extensions);
|
||||
}
|
||||
|
||||
private ExtensionInstallationInfo(ExtensionDetails extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
private static Set<ExtensionInstallationInfo> loadExtensionPointInfo(
|
||||
Set<ExtensionDetails> extensions) {
|
||||
|
||||
// Map all class infos by module so we can then do one lookup per extension. Standardize on
|
||||
// forward slashes for consistency.
|
||||
Set<ClassFileInfo> extensionPoints = ClassSearcher.getExtensionPointInfo();
|
||||
Map<String, List<ClassFileInfo>> classesByModule = extensionPoints.stream()
|
||||
.collect(Collectors.groupingBy(ClassFileInfo::module));
|
||||
|
||||
Set<ExtensionInstallationInfo> results = new HashSet<>();
|
||||
for (ExtensionDetails extension : extensions) {
|
||||
|
||||
ExtensionInstallationInfo info = new ExtensionInstallationInfo(extension);
|
||||
results.add(info);
|
||||
File installDir = extension.getInstallDir();
|
||||
String path = installDir.getAbsolutePath();
|
||||
List<ClassFileInfo> classes = classesByModule.get(path);
|
||||
if (classes != null) {
|
||||
info.classInfos.addAll(classes);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public ExtensionDetails getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public Set<ClassFileInfo> getClassInfos() {
|
||||
return classInfos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return extension.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExtensionInstallationInfo other = (ExtensionInstallationInfo) obj;
|
||||
return Objects.equals(extension, other.extension);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -46,7 +46,7 @@ import utility.application.ApplicationLayout;
|
||||
*
|
||||
* <p>
|
||||
* Extensions may be installed/uninstalled by users at runtime, using the
|
||||
* {@link ExtensionTableProvider}. Installation consists of unzipping the extension archive to an
|
||||
* {@link ExtensionTableDialog}. Installation consists of unzipping the extension archive to an
|
||||
* installation folder, currently <code>{ghidra user settings dir}/Extensions</code>. To uninstall,
|
||||
* the unpacked folder is simply removed.
|
||||
*/
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/* ###
|
||||
* 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.extensions;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.extensions.ExtensionDetails;
|
||||
|
||||
class ExtensionRowObject {
|
||||
|
||||
private ExtensionDetails extension;
|
||||
private ExtensionInstallationInfo info;
|
||||
|
||||
ExtensionRowObject(ExtensionDetails extension) {
|
||||
this.extension = Objects.requireNonNull(extension);
|
||||
}
|
||||
|
||||
ExtensionRowObject(ExtensionDetails extension, ExtensionInstallationInfo info) {
|
||||
this(extension);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public ExtensionDetails getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public Set<ClassFileInfo> getClassInfos() {
|
||||
if (info == null) {
|
||||
return Set.of(); // not installed
|
||||
}
|
||||
return info.getClassInfos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return extension.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExtensionRowObject other = (ExtensionRowObject) obj;
|
||||
return Objects.equals(extension, other.extension);
|
||||
}
|
||||
|
||||
}
|
||||
+46
-44
@@ -42,7 +42,7 @@ import resources.Icons;
|
||||
* Component Provider that shows the known extensions in Ghidra in a {@link GTable}. Users may
|
||||
* install/uninstall extensions, or add new ones.
|
||||
*/
|
||||
public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
public class ExtensionTableDialog extends DialogComponentProvider {
|
||||
|
||||
private static final String LAST_IMPORT_DIRECTORY_KEY = "LastExtensionImportDirectory";
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
*
|
||||
* @param tool the plugin tool
|
||||
*/
|
||||
public ExtensionTableProvider(PluginTool tool) {
|
||||
public ExtensionTableDialog(PluginTool tool) {
|
||||
super("Install Extensions");
|
||||
addWorkPanel(createMainPanel(tool));
|
||||
setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "Extensions"));
|
||||
@@ -73,12 +73,11 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
|
||||
extensionTablePanel = new ExtensionTablePanel(tool);
|
||||
extensionTablePanel.getAccessibleContext().setAccessibleName("Extenstion Table");
|
||||
ExtensionDetailsPanel extensionDetailsPanel =
|
||||
new ExtensionDetailsPanel(extensionTablePanel);
|
||||
extensionDetailsPanel.getAccessibleContext().setAccessibleName("Extension Details");
|
||||
ExtensionDetailsPanel detailsPanel = new ExtensionDetailsPanel(extensionTablePanel);
|
||||
detailsPanel.getAccessibleContext().setAccessibleName("Extension Details");
|
||||
|
||||
final JSplitPane splitPane =
|
||||
new JSplitPane(JSplitPane.VERTICAL_SPLIT, extensionTablePanel, extensionDetailsPanel);
|
||||
JSplitPane splitPane =
|
||||
new JSplitPane(JSplitPane.VERTICAL_SPLIT, extensionTablePanel, detailsPanel);
|
||||
splitPane.setResizeWeight(.75);
|
||||
splitPane.getAccessibleContext().setAccessibleName("Extension Table and Details");
|
||||
panel.add(splitPane, BorderLayout.CENTER);
|
||||
@@ -86,7 +85,7 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
splitPane.setDividerLocation(.75);
|
||||
|
||||
createAddAction(extensionTablePanel);
|
||||
createRefreshAction(extensionTablePanel, extensionDetailsPanel);
|
||||
createRefreshAction(extensionTablePanel, detailsPanel);
|
||||
|
||||
addOKButton();
|
||||
panel.getAccessibleContext().setAccessibleName("Extension Table Provider");
|
||||
@@ -129,41 +128,12 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
return false;
|
||||
}
|
||||
Object contextObject = context.getContextObject();
|
||||
return ExtensionTableProvider.this == contextObject;
|
||||
return ExtensionTableDialog.this == contextObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
|
||||
// Don't let the user attempt to install anything if they don't have write
|
||||
// permissions on the installation dir.
|
||||
ResourceFile installDir =
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension " +
|
||||
"installation directory: " + installDir);
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on " +
|
||||
"installation directory: " + installDir);
|
||||
return;
|
||||
}
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
||||
chooser.setLastDirectoryPreference(LAST_IMPORT_DIRECTORY_KEY);
|
||||
chooser.setTitle("Select Extension");
|
||||
chooser.addFileFilter(new ExtensionFileFilter());
|
||||
|
||||
List<File> files = chooser.getSelectedFiles();
|
||||
chooser.dispose();
|
||||
|
||||
if (installExtensions(files)) {
|
||||
panel.refreshTable();
|
||||
requireRestart = true;
|
||||
}
|
||||
doAddExtension(panel);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,6 +145,38 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
addAction(addAction);
|
||||
}
|
||||
|
||||
private void doAddExtension(ExtensionTablePanel panel) {
|
||||
// Don't let the user attempt to install anything if they don't have write
|
||||
// permissions on the installation dir.
|
||||
List<ResourceFile> dirs = Application.getApplicationLayout().getExtensionInstallationDirs();
|
||||
ResourceFile installDir = dirs.get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension " +
|
||||
"installation directory: " + installDir);
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on " +
|
||||
"installation directory: " + installDir);
|
||||
return;
|
||||
}
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
||||
chooser.setLastDirectoryPreference(LAST_IMPORT_DIRECTORY_KEY);
|
||||
chooser.setTitle("Select Extension");
|
||||
chooser.addFileFilter(new ExtensionFileFilter());
|
||||
|
||||
List<File> files = chooser.getSelectedFiles();
|
||||
chooser.dispose();
|
||||
|
||||
if (installExtensions(files)) {
|
||||
panel.refreshTable();
|
||||
requireRestart = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean installExtensions(List<File> files) {
|
||||
boolean didInstall = false;
|
||||
for (File file : files) {
|
||||
@@ -183,9 +185,9 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
// instead of a fully built extension.
|
||||
if (new File(file, "build.gradle").isFile()) {
|
||||
Msg.showWarn(this, null, "Invalid Extension",
|
||||
"The selected extension " +
|
||||
"contains a 'build.gradle' file.\nGhidra does not support installing " +
|
||||
"extensions in source form.\nPlease build the extension and try again.");
|
||||
"The selected extension contains a 'build.gradle' file.\n" +
|
||||
"Ghidra does not support installing extensions in source form.\n" +
|
||||
"Please build the extension and try again.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an action to refresh the extensions list.
|
||||
* Creates an action to refresh the extensions list.
|
||||
*
|
||||
* @param tablePanel the table to be refreshed
|
||||
* @param detailsPanel the details to be refreshed
|
||||
@@ -210,7 +212,7 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
Object contextObject = context.getContextObject();
|
||||
return ExtensionTableProvider.this == contextObject;
|
||||
return ExtensionTableDialog.this == contextObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
+71
-56
@@ -37,18 +37,19 @@ import ghidra.util.task.TaskMonitor;
|
||||
* Model for the {@link ExtensionTablePanel}. This defines 5 columns for displaying information in
|
||||
* {@link ExtensionDetails} objects:
|
||||
* <pre>
|
||||
* - Installed (checkbox)
|
||||
* - Installation Status
|
||||
* - Name
|
||||
* - Description
|
||||
* - Installation directory (hidden)
|
||||
* - Archive directory (hidden)
|
||||
* - Version
|
||||
* - Installation Directory (hidden)
|
||||
* - Archive File (hidden)
|
||||
* </pre>
|
||||
* <p>
|
||||
* All columns are for display purposes only, except for the <code>installed</code> column, which
|
||||
* is a checkbox allowing users to install/uninstall a particular extension.
|
||||
*
|
||||
*/
|
||||
class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
class ExtensionTableModel extends ThreadedTableModel<ExtensionRowObject, Object> {
|
||||
|
||||
/** We don't care about the ordering of other columns, but the install/uninstall checkbox should be
|
||||
the first one and the name col is our initial sort column. */
|
||||
@@ -56,7 +57,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
final static int NAME_COL = 1;
|
||||
|
||||
/** This is the data source for the model. Whatever is here will be displayed in the table. */
|
||||
private Set<ExtensionDetails> extensions;
|
||||
private Set<ExtensionRowObject> extensions;
|
||||
private Map<String, Boolean> originalInstallStates = new HashMap<>();
|
||||
|
||||
/**
|
||||
@@ -69,9 +70,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<ExtensionDetails> createTableColumnDescriptor() {
|
||||
protected TableColumnDescriptor<ExtensionRowObject> createTableColumnDescriptor() {
|
||||
|
||||
TableColumnDescriptor<ExtensionDetails> descriptor = new TableColumnDescriptor<>();
|
||||
TableColumnDescriptor<ExtensionRowObject> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new ExtensionInstalledColumn(), INSTALLED_COL, true);
|
||||
descriptor.addVisibleColumn(new ExtensionNameColumn(), NAME_COL, true);
|
||||
@@ -111,27 +112,23 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
|
||||
// We only care about the install column here, as it's the only one that
|
||||
// is editable.
|
||||
// We only care about the install column here, as it's the only one that is editable.
|
||||
if (columnIndex != INSTALLED_COL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user does not have write permissions on the installation dir, they cannot
|
||||
// install.
|
||||
ResourceFile installDir =
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
// If the user does not have write permissions on the installation dir, they cannot install.
|
||||
List<ResourceFile> dirs = Application.getApplicationLayout().getExtensionInstallationDirs();
|
||||
ResourceFile installDir = dirs.get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation " +
|
||||
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||
"Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Failed to create installation directory.\n" +
|
||||
"See the 'Ghidra Extension Notes' section of the Ghidra Installation Guide.");
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation " +
|
||||
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||
"Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Cannot write to installation directory.\n" +
|
||||
"See the 'Ghidra Extension Notes' section of the Ghidra Installation Guide.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,8 +162,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
|
||||
// This is a programming error
|
||||
Msg.error(this,
|
||||
"Unable install an extension that no longer exists. Restart Ghidra and " +
|
||||
"try manually installing the extension: '" + extension.getName() + "'");
|
||||
"Unable install an extension that no longer exists.\n" +
|
||||
"Restart Ghidra and try manually installing the extension: '" +
|
||||
extension.getName() + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +185,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<ExtensionDetails> accumulator, TaskMonitor monitor)
|
||||
protected void doLoad(Accumulator<ExtensionRowObject> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (extensions != null) {
|
||||
accumulator.addAll(extensions);
|
||||
@@ -196,22 +194,29 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
|
||||
ExtensionUtils.reload();
|
||||
Set<ExtensionDetails> archived = ExtensionUtils.getArchiveExtensions();
|
||||
Set<ExtensionDetails> installed = ExtensionUtils.getInstalledExtensions();
|
||||
|
||||
// don't show archived extensions that have been installed
|
||||
for (ExtensionDetails extension : installed) {
|
||||
if (archived.remove(extension)) {
|
||||
Msg.trace(this,
|
||||
"Not showing archived extension that has been installed. Archive path: " +
|
||||
extension.getArchivePath()); // useful for debugging
|
||||
}
|
||||
}
|
||||
Set<ExtensionInstallationInfo> installed = ExtensionInstallationInfo.get();
|
||||
|
||||
extensions = new HashSet<>();
|
||||
extensions.addAll(installed);
|
||||
extensions.addAll(archived);
|
||||
|
||||
for (ExtensionDetails e : extensions) {
|
||||
// don't show archived extensions that have been installed
|
||||
for (ExtensionInstallationInfo info : installed) {
|
||||
|
||||
ExtensionDetails e = info.getExtension();
|
||||
if (archived.remove(e)) {
|
||||
Msg.trace(this,
|
||||
"Not showing archived extension that has been installed. Archive path: " +
|
||||
e.getArchivePath()); // useful for debugging
|
||||
}
|
||||
|
||||
extensions.add(new ExtensionRowObject(e, info));
|
||||
}
|
||||
|
||||
for (ExtensionDetails e : archived) {
|
||||
extensions.add(new ExtensionRowObject(e));
|
||||
}
|
||||
|
||||
for (ExtensionRowObject ro : extensions) {
|
||||
ExtensionDetails e = ro.getExtension();
|
||||
String name = e.getName();
|
||||
if (originalInstallStates.containsKey(name)) {
|
||||
continue; // preserve the original value
|
||||
@@ -229,7 +234,8 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
*/
|
||||
public boolean hasModelChanged() {
|
||||
|
||||
for (ExtensionDetails e : extensions) {
|
||||
for (ExtensionRowObject ro : extensions) {
|
||||
ExtensionDetails e = ro.getExtension();
|
||||
Boolean wasInstalled = originalInstallStates.get(e.getName());
|
||||
if (wasInstalled == null) {
|
||||
return false;
|
||||
@@ -248,7 +254,12 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* @param model the list to use as the model
|
||||
*/
|
||||
public void setModelData(List<ExtensionDetails> model) {
|
||||
extensions = new HashSet<>(model);
|
||||
|
||||
extensions = new HashSet<>();
|
||||
for (ExtensionDetails e : model) {
|
||||
extensions.add(new ExtensionRowObject(e));
|
||||
}
|
||||
|
||||
reload();
|
||||
}
|
||||
|
||||
@@ -270,14 +281,18 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* @return the selected extension, or null if nothing is selected
|
||||
*/
|
||||
private ExtensionDetails getSelectedExtension(int row) {
|
||||
return getRowObject(row);
|
||||
ExtensionRowObject ro = getRowObject(row);
|
||||
if (ro != null) {
|
||||
return ro.getExtension();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column for displaying the extension name.
|
||||
*/
|
||||
private class ExtensionNameColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, String, Object> {
|
||||
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@@ -292,9 +307,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public String getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getName();
|
||||
return rowObject.getExtension().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,7 +322,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* Table column for displaying the extension description.
|
||||
*/
|
||||
private class ExtensionDescriptionColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, String, Object> {
|
||||
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@@ -322,9 +337,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public String getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getDescription();
|
||||
return rowObject.getExtension().getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -337,7 +352,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* Table column for displaying the extension description.
|
||||
*/
|
||||
private class ExtensionVersionColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, String, Object> {
|
||||
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@@ -352,10 +367,10 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public String getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
|
||||
String version = rowObject.getVersion();
|
||||
String version = rowObject.getExtension().getVersion();
|
||||
|
||||
// Check for the default version value. If this is still set, then no version has been
|
||||
// established so just display an empty string.
|
||||
@@ -376,7 +391,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* Table column for displaying the extension installation status.
|
||||
*/
|
||||
private class ExtensionInstalledColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, Boolean, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, Boolean, Object> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -389,9 +404,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public Boolean getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.isInstalled();
|
||||
return rowObject.getExtension().isInstalled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +414,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* Table column for displaying the extension installation directory.
|
||||
*/
|
||||
private class ExtensionInstallationDirColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, String, Object> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -412,9 +427,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public String getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getInstallPath();
|
||||
return rowObject.getExtension().getInstallPath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +437,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* Table column for displaying the extension archive file.
|
||||
*/
|
||||
private class ExtensionArchiveFileColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
extends AbstractDynamicTableColumn<ExtensionRowObject, String, Object> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -435,9 +450,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ExtensionDetails rowObject, Settings settings, Object data,
|
||||
public String getValue(ExtensionRowObject rowObject, Settings settings, Object data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getArchivePath();
|
||||
return rowObject.getExtension().getArchivePath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-13
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -36,7 +36,7 @@ import help.HelpService;
|
||||
*/
|
||||
public class ExtensionTablePanel extends JPanel {
|
||||
|
||||
private GTableFilterPanel<ExtensionDetails> tableFilterPanel;
|
||||
private GTableFilterPanel<ExtensionRowObject> tableFilterPanel;
|
||||
private ExtensionTableModel tableModel;
|
||||
private GTable table;
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ExtensionTablePanel extends JPanel {
|
||||
return table;
|
||||
}
|
||||
|
||||
public ExtensionDetails getSelectedItem() {
|
||||
public ExtensionRowObject getSelectedItem() {
|
||||
return tableFilterPanel.getSelectedItem();
|
||||
}
|
||||
|
||||
@@ -96,15 +96,6 @@ public class ExtensionTablePanel extends JPanel {
|
||||
tableModel.refreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter panel.
|
||||
*
|
||||
* @return the filter panel
|
||||
*/
|
||||
public GTableFilterPanel<ExtensionDetails> getFilterPanel() {
|
||||
return tableFilterPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the contents of the table with the given list of extensions.
|
||||
*
|
||||
|
||||
+5
-3
@@ -18,6 +18,8 @@ package ghidra.framework.project.tool;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
|
||||
/**
|
||||
* An interface to help describe extensions' enable state for a given tool.
|
||||
*/
|
||||
@@ -26,18 +28,18 @@ public interface ExtensionsEnabledState {
|
||||
/**
|
||||
* {@return a map of all known extensions to a set of their plugins}
|
||||
*/
|
||||
public Map<String, Set<Class<?>>> getAllKnownExtensions();
|
||||
public Map<String, Set<ClassFileInfo>> getAllKnownExtensions();
|
||||
|
||||
/**
|
||||
* All plugins installed in the current tool will be removed from the given set. This allows the
|
||||
* client to have a set of plugins that are not currently installed.
|
||||
* @param allPlugins the plugins set to update
|
||||
*/
|
||||
public void removeInstalledPlugins(Set<Class<?>> allPlugins);
|
||||
public void removeInstalledPlugins(Set<ClassFileInfo> allPlugins);
|
||||
|
||||
/**
|
||||
* Shows a window to prompt the user to configure any new extension plugins.
|
||||
* @param newPlugins the new extension plugins
|
||||
*/
|
||||
public void propmtToConfigureNewPlugins(Set<Class<?>> newPlugins);
|
||||
public void propmtToConfigureNewPlugins(Set<ClassFileInfo> newPlugins);
|
||||
}
|
||||
|
||||
+62
-105
@@ -16,26 +16,25 @@
|
||||
package ghidra.framework.project.tool;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.json.Json;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.dialog.PluginInstallerDialog;
|
||||
import ghidra.framework.plugintool.util.PluginDescription;
|
||||
import ghidra.framework.project.extensions.ExtensionInstallationInfo;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.extensions.ExtensionDetails;
|
||||
import ghidra.util.extensions.ExtensionUtils;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* The default extension state for a {@link PluginTool}.
|
||||
*/
|
||||
class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
|
||||
private static String PLUGIN_SUFFIX = Plugin.class.getSimpleName();
|
||||
|
||||
private PluginTool tool;
|
||||
|
||||
ToolExtensionsEnabledState(PluginTool tool) {
|
||||
@@ -43,33 +42,61 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Set<Class<?>>> getAllKnownExtensions() {
|
||||
public Map<String, Set<ClassFileInfo>> getAllKnownExtensions() {
|
||||
|
||||
Set<ExtensionDetails> extensions = getExtensions();
|
||||
Set<ExtensionInstallationInfo> extensions = getExtensions();
|
||||
if (extensions.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
Map<String, Set<Class<?>>> plugins = new HashMap<>();
|
||||
Set<PluginPath> pluginPaths = getAllPluginPaths();
|
||||
for (ExtensionDetails extension : extensions) {
|
||||
Set<Class<?>> classes = findPluginsLoadedFromExtension(extension, pluginPaths);
|
||||
plugins.put(extension.getName(), classes);
|
||||
Map<String, Set<ClassFileInfo>> result = new HashMap<>();
|
||||
for (ExtensionInstallationInfo info : extensions) {
|
||||
|
||||
ExtensionDetails extension = info.getExtension();
|
||||
Set<ClassFileInfo> plugins = getPlugins(info);
|
||||
if (plugins.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.put(extension.getName(), plugins);
|
||||
}
|
||||
return plugins;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<ClassFileInfo> getPlugins(ExtensionInstallationInfo info) {
|
||||
|
||||
Set<ClassFileInfo> result = new HashSet<>();
|
||||
Set<ClassFileInfo> classInfos = info.getClassInfos();
|
||||
for (ClassFileInfo classInfo : classInfos) {
|
||||
String suffix = classInfo.suffix();
|
||||
if (PLUGIN_SUFFIX.equals(suffix)) {
|
||||
result.add(classInfo);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeInstalledPlugins(Set<Class<?>> plugins) {
|
||||
public void removeInstalledPlugins(Set<ClassFileInfo> plugins) {
|
||||
List<Plugin> activePlugins = tool.getManagedPlugins();
|
||||
for (Plugin plugin : activePlugins) {
|
||||
Class<? extends Plugin> clazz = plugin.getClass();
|
||||
plugins.remove(clazz);
|
||||
|
||||
Set<String> activeClassNames = activePlugins.stream()
|
||||
.map(Plugin::getClass)
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Iterator<ClassFileInfo> it = plugins.iterator();
|
||||
while (it.hasNext()) {
|
||||
ClassFileInfo info = it.next();
|
||||
String name = info.name();
|
||||
if (activeClassNames.contains(name)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propmtToConfigureNewPlugins(Set<Class<?>> plugins) {
|
||||
public void propmtToConfigureNewPlugins(Set<ClassFileInfo> plugins) {
|
||||
// Offer the user a chance to configure any newly discovered plugins
|
||||
int option = OptionDialog.showYesNoDialog(tool.getToolFrame(), "New Plugins Found!",
|
||||
"New extension plugins detected. Would you like to configure them?");
|
||||
@@ -81,20 +108,20 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<PluginPath> getAllPluginPaths() {
|
||||
Set<PluginPath> paths = new HashSet<>();
|
||||
List<Class<? extends Plugin>> plugins = ClassSearcher.getClasses(Plugin.class);
|
||||
for (Class<? extends Plugin> plugin : plugins) {
|
||||
paths.add(new PluginPath(plugin));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
private static Set<ExtensionInstallationInfo> getExtensions() {
|
||||
|
||||
private static Set<ExtensionDetails> getExtensions() {
|
||||
Set<ExtensionDetails> installedExtensions = ExtensionUtils.getActiveInstalledExtensions();
|
||||
return installedExtensions.stream()
|
||||
.filter(e -> !isRepoExtension(e))
|
||||
.collect(Collectors.toSet());
|
||||
Set<ExtensionInstallationInfo> infos = ExtensionInstallationInfo.get();
|
||||
Iterator<ExtensionInstallationInfo> it = infos.iterator();
|
||||
while (it.hasNext()) {
|
||||
|
||||
ExtensionInstallationInfo info = it.next();
|
||||
ExtensionDetails e = info.getExtension();
|
||||
if (isRepoExtension(e) || e.isPendingUninstall()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,45 +144,6 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all plugin classes loaded from a particular extension folder.
|
||||
* <p>
|
||||
* This uses the {@link ClassSearcher} to find all <code>Plugin.class</code> objects on the
|
||||
* classpath. For each class, the original resource file is compared against the
|
||||
* given extension folder and the jar files for that extension.
|
||||
*
|
||||
* @param extension the extension from which to find plugins
|
||||
* @param pluginPaths all loaded plugin paths
|
||||
* @return list of {@link Plugin} classes, or empty list if none found
|
||||
*/
|
||||
private static Set<Class<?>> findPluginsLoadedFromExtension(ExtensionDetails extension,
|
||||
Set<PluginPath> pluginPaths) {
|
||||
|
||||
if (!extension.isInstalled()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Find any jar files in the directory provided
|
||||
Set<URL> jarPaths = extension.getLibraries();
|
||||
|
||||
// Now get all Plugin.class file paths and see if any of them were loaded from one of the
|
||||
// extension the given extension directory
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
for (PluginPath pluginPath : pluginPaths) {
|
||||
if (pluginPath.isFrom(extension.getInstallDir())) {
|
||||
result.add(pluginPath.getPluginClass());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (URL jarUrl : jarPaths) {
|
||||
if (pluginPath.isFrom(jarUrl)) {
|
||||
result.add(pluginPath.getPluginClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -167,7 +155,7 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
* @param plugins the list of plugin classes to search for
|
||||
* @return list of plugin descriptions
|
||||
*/
|
||||
private List<PluginDescription> getPluginDescriptions(Set<Class<?>> plugins) {
|
||||
private List<PluginDescription> getPluginDescriptions(Set<ClassFileInfo> plugins) {
|
||||
|
||||
// First define the list of plugin descriptions to return
|
||||
List<PluginDescription> descriptions = new ArrayList<>();
|
||||
@@ -178,11 +166,11 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
pluginsConfiguration.getManagedPluginDescriptions();
|
||||
|
||||
// see if an entry exists in the list of all loaded plugins
|
||||
for (Class<?> plugin : plugins) {
|
||||
String pluginName = plugin.getSimpleName();
|
||||
for (ClassFileInfo info : plugins) {
|
||||
String pluginName = info.simpleName();
|
||||
|
||||
Optional<PluginDescription> desc = allPluginDescriptions.stream()
|
||||
.filter(d -> (pluginName.equals(d.getName())))
|
||||
.filter(d -> pluginName.equals(d.getName()))
|
||||
.findAny();
|
||||
if (desc.isPresent()) {
|
||||
descriptions.add(desc.get());
|
||||
@@ -192,35 +180,4 @@ class ToolExtensionsEnabledState implements ExtensionsEnabledState {
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
private static class PluginPath {
|
||||
private Class<? extends Plugin> pluginClass;
|
||||
private String pluginLocation;
|
||||
private File pluginFile;
|
||||
|
||||
PluginPath(Class<? extends Plugin> pluginClass) {
|
||||
this.pluginClass = pluginClass;
|
||||
String name = pluginClass.getName();
|
||||
URL url = pluginClass.getResource('/' + name.replace('.', '/') + ".class");
|
||||
this.pluginLocation = url.getPath();
|
||||
this.pluginFile = new File(pluginLocation);
|
||||
}
|
||||
|
||||
public boolean isFrom(File dir) {
|
||||
return FileUtilities.isPathContainedWithin(dir, pluginFile);
|
||||
}
|
||||
|
||||
boolean isFrom(URL jarUrl) {
|
||||
String jarPath = jarUrl.getPath();
|
||||
return pluginLocation.contains(jarPath);
|
||||
}
|
||||
|
||||
Class<? extends Plugin> getPluginClass() {
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-12
@@ -25,6 +25,7 @@ import org.jdom2.Element;
|
||||
|
||||
import ghidra.util.NumericUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.classfinder.ClassFileInfo;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ class ToolExtensionsStatusManager {
|
||||
private static final String XML_ATTR_EXTENSION_NAME = "NAME";
|
||||
private static final String XML_ATTR_EXTENSION_PLUGIN_CLASS = "CLASS";
|
||||
|
||||
private Set<Class<?>> newExtensionPlugins = new HashSet<>();
|
||||
private Set<ClassFileInfo> newExtensionPlugins = new HashSet<>();
|
||||
private ExtensionsEnabledState extensionsState;
|
||||
|
||||
ToolExtensionsStatusManager(ExtensionsEnabledState extensionState) {
|
||||
@@ -60,21 +61,21 @@ class ToolExtensionsStatusManager {
|
||||
|
||||
void saveToXml(Element xml) {
|
||||
|
||||
Map<String, Set<Class<?>>> pluginsByExtension =
|
||||
Map<String, Set<ClassFileInfo>> pluginsByExtension =
|
||||
extensionsState.getAllKnownExtensions();
|
||||
Element extensionsParent = new Element(XML_TAG_EXTENSIONS);
|
||||
|
||||
Set<Entry<String, Set<Class<?>>>> entries = pluginsByExtension.entrySet();
|
||||
for (Entry<String, Set<Class<?>>> entry : entries) {
|
||||
Set<Entry<String, Set<ClassFileInfo>>> entries = pluginsByExtension.entrySet();
|
||||
for (Entry<String, Set<ClassFileInfo>> entry : entries) {
|
||||
String name = entry.getKey();
|
||||
|
||||
Element extensionsElement = new Element(XML_TAG_EXTENSION);
|
||||
setExtensionName(extensionsElement, name);
|
||||
extensionsParent.addContent(extensionsElement);
|
||||
|
||||
Set<Class<?>> plugins = entry.getValue();
|
||||
for (Class<?> clazz : plugins) {
|
||||
String className = clazz.getName();
|
||||
Set<ClassFileInfo> infos = entry.getValue();
|
||||
for (ClassFileInfo info : infos) {
|
||||
String className = info.name();
|
||||
Element pluginElement = new Element(XML_TAG_PLUGIN);
|
||||
pluginElement.setAttribute(XML_ATTR_EXTENSION_PLUGIN_CLASS, className);
|
||||
extensionsElement.addContent(pluginElement);
|
||||
@@ -98,7 +99,7 @@ class ToolExtensionsStatusManager {
|
||||
|
||||
5) Save the new extension plugins for later user prompting when saving to xml.
|
||||
*/
|
||||
Map<String, Set<Class<?>>> extensionPlugins = extensionsState.getAllKnownExtensions();
|
||||
Map<String, Set<ClassFileInfo>> extensionPlugins = extensionsState.getAllKnownExtensions();
|
||||
Map<String, ExtensionMemento> xmlMementosByName = getKnownExtensions(xml);
|
||||
Set<String> names = extensionPlugins.keySet();
|
||||
Set<String> newExtensions = new HashSet<>(names);
|
||||
@@ -115,7 +116,7 @@ class ToolExtensionsStatusManager {
|
||||
}
|
||||
}
|
||||
|
||||
Set<Class<?>> newPlugins = newExtensions.stream()
|
||||
Set<ClassFileInfo> newPlugins = newExtensions.stream()
|
||||
.map(name -> extensionPlugins.get(name)) // classes by extension name
|
||||
.flatMap(set -> set.stream()) // map all sets to a single stream
|
||||
.collect(Collectors.toSet());
|
||||
@@ -129,19 +130,19 @@ class ToolExtensionsStatusManager {
|
||||
}
|
||||
|
||||
private static boolean hasNewPlugins(ExtensionMemento xmlMemento,
|
||||
Map<String, Set<Class<?>>> pluginsByExtensionName) {
|
||||
Map<String, Set<ClassFileInfo>> pluginsByExtensionName) {
|
||||
|
||||
// If the xml memento is empty, it is either the old style xml that did not save plugin
|
||||
// names or is the new style xml, but the extension did not previously have any plugins. In
|
||||
// this case, we want to prompt the user if there are plugins to install.
|
||||
Set<String> xmlClassNames = xmlMemento.pluginClassNames();
|
||||
Set<Class<?>> cpPluginClasses = pluginsByExtensionName.get(xmlMemento.name());
|
||||
Set<ClassFileInfo> cpPluginClasses = pluginsByExtensionName.get(xmlMemento.name());
|
||||
if (xmlClassNames.isEmpty() && !cpPluginClasses.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<String> cpNames = cpPluginClasses.stream()
|
||||
.map(c -> c.getName())
|
||||
.map(c -> c.name())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
cpNames.removeAll(xmlClassNames);
|
||||
|
||||
+3
-3
@@ -43,7 +43,7 @@ import ghidra.framework.main.wizard.project.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.project.extensions.ExtensionTablePanel;
|
||||
import ghidra.framework.project.extensions.ExtensionTableProvider;
|
||||
import ghidra.framework.project.extensions.ExtensionTableDialog;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
@@ -190,8 +190,8 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
performAction("Extensions", "Project Window", false);
|
||||
waitForSwing();
|
||||
|
||||
ExtensionTableProvider provider =
|
||||
(ExtensionTableProvider) getDialog(ExtensionTableProvider.class);
|
||||
ExtensionTableDialog provider =
|
||||
(ExtensionTableDialog) getDialog(ExtensionTableDialog.class);
|
||||
Object panel = getInstanceField("extensionTablePanel", provider);
|
||||
GTable table = (GTable) getInstanceField("table", panel);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user