diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 0b52408eca..cfd3933d5e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -19,6 +19,8 @@ import java.io.*; import java.net.URL; import java.nio.file.*; import java.util.*; +import java.util.Map.Entry; +import java.util.function.Function; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,33 +36,23 @@ import org.osgi.framework.wiring.*; import org.osgi.service.log.*; import generic.jar.ResourceFile; -import ghidra.app.script.GhidraScriptUtil; import ghidra.framework.Application; +import ghidra.framework.options.SaveState; +import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.*; public class BundleHost { protected static final boolean DUMP_TO_STDERR = false; - // XXX ScriptProviders don't have any way to access services or other way to access system wide resources - static private BundleHost _instance; - - static public BundleHost getInstance() { - if (_instance == null) { - _instance = new BundleHost(); - try { - _instance.startFelix(); - } - catch (OSGiException | IOException e) { - throw new RuntimeException(e); - } - } - return _instance; - } static public String getSymbolicNameFromSourceDir(ResourceFile sourceDir) { return Integer.toHexString(sourceDir.getAbsolutePath().hashCode()); } + public BundleHost() { + // + } + public void dispose() { if (felix != null) { forceStopFelix(); @@ -68,25 +60,59 @@ public class BundleHost { } HashMap bp2gb = new HashMap<>(); + HashMap bl2gb = new HashMap<>(); - public GhidraBundle getGhidraBundle(ResourceFile path) { - return bp2gb.computeIfAbsent(path, p -> newGhidraBundle0(path, true, false)); + public GhidraBundle getExistingGhidraBundle(ResourceFile bundlePath) { + GhidraBundle r = bp2gb.get(bundlePath); + if (r == null) { + Msg.showError(this, null, "ghidra bundle cache", + "getExistingGhidraBundle before GhidraBundle created: " + bundlePath); + } + return r; } - public GhidraBundle newGhidraBundle(ResourceFile path, boolean enabled, boolean systemBundle) { - GhidraBundle gb = newGhidraBundle0(path, enabled, systemBundle); - bp2gb.put(path, gb); + public GhidraBundle enablePath(ResourceFile bundlePath) { + GhidraBundle gb = bp2gb.get(bundlePath); + if (gb == null) { + gb = addGhidraBundle(bundlePath, true, false); + } return gb; } - private GhidraBundle newGhidraBundle0(ResourceFile path, boolean enabled, + public GhidraBundle addGhidraBundle(ResourceFile path, boolean enabled, boolean systemBundle) { + GhidraBundle gb = newGhidraBundle(this, path, enabled, systemBundle); + bp2gb.put(path, gb); + bl2gb.put(gb.getBundleLoc(), gb); + fireBundleAdded(gb); + return gb; + } + + public void addGhidraBundles(List paths, boolean enabled, boolean systemBundle) { + Map newmap = + paths.stream().collect(Collectors.toUnmodifiableMap(Function.identity(), + path -> newGhidraBundle(BundleHost.this, path, enabled, systemBundle))); + bp2gb.putAll(newmap); + bl2gb.putAll(newmap.values().stream().collect( + Collectors.toUnmodifiableMap(GhidraBundle::getBundleLoc, Function.identity()))); + fireBundlesAdded(newmap.values()); + } + + public void add(List gbundles) { + for (GhidraBundle gb : gbundles) { + bp2gb.put(gb.getPath(), gb); + bl2gb.put(gb.getBundleLoc(), gb); + } + fireBundlesAdded(gbundles); + } + + static private GhidraBundle newGhidraBundle(BundleHost bh, ResourceFile path, boolean enabled, boolean systemBundle) { switch (GhidraBundle.getType(path)) { case SourceDir: - return new GhidraSourceBundle(this, path, enabled, systemBundle); + return new GhidraSourceBundle(bh, path, enabled, systemBundle); case BndScript: case Jar: - return new GhidraJarBundle(this, path, enabled, systemBundle); + return new GhidraJarBundle(bh, path, enabled, systemBundle); default: break; } @@ -94,8 +120,30 @@ public class BundleHost { } // XXX consumers must clean up after themselves - public void removeGhidraBundle(ResourceFile path) { - bp2gb.remove(path); + public void removeBundlePath(ResourceFile bundlePath) { + GhidraBundle gb = bp2gb.remove(bundlePath); + bl2gb.remove(gb.getBundleLoc()); + fireBundleRemoved(gb); + } + + public void removeBundleLoc(String bundleLoc) { + GhidraBundle gb = bl2gb.remove(bundleLoc); + bp2gb.remove(gb.getPath()); + fireBundleRemoved(gb); + } + + public void remove(GhidraBundle gb) { + bp2gb.remove(gb.getPath()); + bl2gb.remove(gb.getBundleLoc()); + fireBundleRemoved(gb); + } + + public void remove(Collection gbundles) { + for (GhidraBundle gb : gbundles) { + bp2gb.remove(gb.getPath()); + bl2gb.remove(gb.getBundleLoc()); + } + fireBundlesRemoved(gbundles); } /** @@ -343,12 +391,15 @@ public class BundleHost { String l = b.getLocation(); System.err.printf("%s %s from %s\n", getEventTypeString(event), n, l); } + GhidraBundle gb; switch (event.getType()) { case BundleEvent.STARTED: - fireBundleActivationChange(b, true); + gb = bl2gb.get(b.getLocation()); + fireBundleActivationChange(gb, true); break; case BundleEvent.UNINSTALLED: - fireBundleActivationChange(b, false); + gb = bl2gb.get(b.getLocation()); + fireBundleActivationChange(gb, false); break; default: break; @@ -544,55 +595,159 @@ public class BundleHost { } } - List osgiListeners = new ArrayList<>(); + List listeners = new ArrayList<>(); void fireBundleBuilt(GhidraBundle sbi) { - synchronized (osgiListeners) { - for (OSGiListener l : osgiListeners) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { l.bundleBuilt(sbi); } } } - void fireBundleActivationChange(Bundle b, boolean newActivation) { - synchronized (osgiListeners) { - for (OSGiListener l : osgiListeners) { - l.bundleActivationChange(b, newActivation); + void fireBundleEnablementChange(GhidraBundle gb, boolean newEnablement) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundleEnablementChange(gb, newEnablement); } } } - public void addListener(OSGiListener osgiListener) { - synchronized (osgiListeners) { - osgiListeners.add(osgiListener); + void fireBundleActivationChange(GhidraBundle gb, boolean newEnablement) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundleActivationChange(gb, newEnablement); + } } } - public void removeListener(OSGiListener osgiListener) { - synchronized (osgiListeners) { - osgiListeners.remove(osgiListener); + private void fireBundleAdded(GhidraBundle gb) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundleAdded(gb); + } + } + } + + private void fireBundleRemoved(GhidraBundle gb) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundleRemoved(gb); + } + } + } + + private void fireBundlesAdded(Collection gbundles) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundlesAdded(gbundles); + } + } + } + + private void fireBundlesRemoved(Collection gbundles) { + synchronized (listeners) { + for (BundleHostListener l : listeners) { + l.bundlesRemoved(gbundles); + } + } + } + + public void addListener(BundleHostListener bundleHostListener) { + synchronized (listeners) { + listeners.add(bundleHostListener); + } + } + + public void removeListener(BundleHostListener bundleHostListener) { + synchronized (listeners) { + listeners.remove(bundleHostListener); } } public void removeAllListeners() { - synchronized (osgiListeners) { - osgiListeners.clear(); + synchronized (listeners) { + listeners.clear(); } } - private List bundlePaths = new ArrayList<>(); - - { - setBundlePaths(GhidraScriptUtil.getSystemScriptPaths()); - getBundlePaths().add(0, GhidraScriptUtil.getUserScriptDirectory()); + public Collection getGhidraBundles() { + return bp2gb.values(); } - public List getBundlePaths() { - return bundlePaths; + public Collection getBundlePaths() { + return bp2gb.keySet(); } - public void setBundlePaths(List bundlePaths) { - this.bundlePaths = bundlePaths; + public boolean enable(GhidraBundle gbundle) { + if (!gbundle.enabled) { + gbundle.enabled = true; + fireBundleEnablementChange(gbundle, true); + } + return false; + } + + /* + * this is done at most once, and it's done AFTER system bundles have been added. + */ + public void restoreState(SaveState ss) { + // XXX lock bundlehost operations + String[] pathArr = ss.getStrings("BundleHost_PATH", new String[0]); + + if (pathArr.length == 0) { + return; + } + + boolean[] enableArr = ss.getBooleans("BundleHost_ENABLE", new boolean[pathArr.length]); + boolean[] systemArr = ss.getBooleans("BundleHost_SYSTEM", new boolean[pathArr.length]); + + List added = new ArrayList<>(); + + for (int i = 0; i < pathArr.length; i++) { + ResourceFile bp = new ResourceFile(pathArr[i]); + boolean en = enableArr[i]; + boolean sys = systemArr[i]; + GhidraBundle gb = bp2gb.get(bp); + if (gb != null) { + if (en != gb.isEnabled()) { + gb.enabled = en; + fireBundleEnablementChange(gb, en); + } + if (sys != gb.isSystemBundle()) { + gb.systemBundle = sys; + Msg.error(this, String.format("%s went from %system to %system", bp, + sys ? "not " : "", sys ? "" : "not ")); + } + } + else if (sys) { + // stored system bundles that weren't already initialized must be old, drop 'm. + } + else { + added.add(newGhidraBundle(this, bp, en, sys)); + } + } + + add(added); + } + + public void saveState(SaveState ss) { + int n = bp2gb.size(); + String[] pathArr = new String[n]; + boolean[] enableArr = new boolean[n]; + boolean[] systemArr = new boolean[n]; + + int index = 0; + for (Entry e : bp2gb.entrySet()) { + GhidraBundle gb = e.getValue(); + pathArr[index] = gb.getPath().toString(); + enableArr[index] = gb.isEnabled(); + systemArr[index] = gb.isSystemBundle(); + ++index; + } + + ss.putStrings("BundleHost_PATH", pathArr); + ss.putBooleans("BundleHost_ENABLE", enableArr); + ss.putBooleans("BundleHost_SYSTEM", systemArr); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHostListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHostListener.java new file mode 100644 index 0000000000..34da1221de --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHostListener.java @@ -0,0 +1,42 @@ +package ghidra.app.plugin.core.osgi; + +import java.util.Collection; + +/** + * Listener for OSGi framework events. + */ +public interface BundleHostListener { + + default void bundleBuilt(GhidraBundle gbundle) { + // + } + + default void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablement) { + // + } + + default void bundleActivationChange(GhidraBundle gbundle, boolean newActivation) { + // + } + + default void bundleAdded(GhidraBundle gbundle) { + // + } + + default void bundlesAdded(Collection gbundles) { + for (GhidraBundle gbundle : gbundles) { + bundleAdded(gbundle); + } + } + + default void bundleRemoved(GhidraBundle gbundle) { + // + } + + default void bundlesRemoved(Collection gbundles) { + for (GhidraBundle gbundle : gbundles) { + bundleRemoved(gbundle); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusChangeRequestListener.java similarity index 64% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusListener.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusChangeRequestListener.java index 3f06386abe..96ae455a5b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusChangeRequestListener.java @@ -17,22 +17,15 @@ package ghidra.app.plugin.core.osgi; /** - * events thrown by BundleStatusModel component + * events thrown by BundleStatus component when buttons are clicked */ -public interface BundleStatusListener { +public interface BundleStatusChangeRequestListener { - /** - * Called when the list of bundle paths changes - */ - default public void bundlesChanged() { + default public void bundleEnablementChangeRequest(BundleStatus status, boolean newValue) { // } - default public void bundleEnablementChanged(BundleStatus status, boolean newValue) { - // - } - - default public void bundleActivationChanged(BundleStatus status, boolean newValue) { + default public void bundleActivationChangeRequest(BundleStatus status, boolean newValue) { // } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java similarity index 88% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusProvider.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java index 8378eebbe7..fe127ccbfe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java @@ -46,12 +46,12 @@ import resources.ResourceManager; /** * component for managing OSGi bundle status */ -public class BundleStatusProvider extends ComponentProviderAdapter { +public class BundleStatusComponentProvider extends ComponentProviderAdapter { static String preferenceForLastSelectedBundle = "LastGhidraScriptBundle"; private JPanel panel; private LessFreneticGTable bundleStatusTable; - private final BundleStatusModel bundleStatusModel; + private final BundleStatusTableModel bundleStatusTableModel; private GTableFilterPanel filterPanel; private GhidraFileChooser fileChooser; @@ -61,23 +61,24 @@ public class BundleStatusProvider extends ComponentProviderAdapter { static final String BUNDLE_GROUP = "0bundle group"; static final String BUNDLE_LIST_GROUP = "1bundle list group"; - public BundleStatusProvider(PluginTool tool, String owner) { - super(tool, "Bundle Status Manager", owner); - this.bundleHost = BundleHost.getInstance(); - this.bundleStatusModel = new BundleStatusModel(this, bundleHost); + public BundleStatusComponentProvider(PluginTool tool, String owner, BundleHost bundleHost) { + super(tool, "Bundle Status Component", owner); + this.bundleHost = bundleHost; + this.bundleStatusTableModel = new BundleStatusTableModel(this, bundleHost); - bundleStatusModel.addListener(new BundleStatusListener() { + bundleStatusTableModel.addListener(new BundleStatusChangeRequestListener() { @Override - public void bundleEnablementChanged(BundleStatus status, boolean enabled) { + public void bundleEnablementChangeRequest(BundleStatus status, boolean enabled) { if (!enabled && status.isActive()) { startActivateDeactiveTask(status, false); } } @Override - public void bundleActivationChanged(BundleStatus status, boolean newValue) { + public void bundleActivationChangeRequest(BundleStatus status, boolean newValue) { startActivateDeactiveTask(status, newValue); } + }); this.filter = new GhidraFileFilter() { @@ -101,7 +102,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { private void build() { panel = new JPanel(new BorderLayout(5, 5)); - bundleStatusTable = new LessFreneticGTable(bundleStatusModel); + bundleStatusTable = new LessFreneticGTable(bundleStatusTableModel); bundleStatusTable.setName("BUNDLESTATUS_TABLE"); bundleStatusTable.setSelectionBackground(new Color(204, 204, 255)); bundleStatusTable.setSelectionForeground(Color.BLACK); @@ -112,7 +113,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { if (e.getValueIsAdjusting()) { return; } - tool.contextChanged(BundleStatusProvider.this); + tool.contextChanged(BundleStatusComponentProvider.this); }); // to allow custom cell renderers @@ -124,14 +125,14 @@ public class BundleStatusProvider extends ComponentProviderAdapter { // column = - bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.enabledColumn.index); + bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.enabledColumn.index); column.setPreferredWidth(skinnyWidth); column.setMinWidth(skinnyWidth); column.setMaxWidth(skinnyWidth); column.setWidth(skinnyWidth); // - column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.activeColumn.index); + column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.activeColumn.index); column.setPreferredWidth(skinnyWidth); column.setMinWidth(skinnyWidth); column.setMaxWidth(skinnyWidth); @@ -159,11 +160,11 @@ public class BundleStatusProvider extends ComponentProviderAdapter { }); // - column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.typeColumn.index); + column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.typeColumn.index); FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont()); column.setMaxWidth(10 + SwingUtilities.computeStringWidth(fontmetrics, GhidraBundle.Type.SourceDir.toString())); - column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index); + column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.pathColumn.index); column.setCellRenderer(new GTableCellRenderer() { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -177,7 +178,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { } }); - filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel); + filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusTableModel); JScrollPane scrollPane = new JScrollPane(bundleStatusTable); scrollPane.getViewport().setBackground(bundleStatusTable.getBackground()); @@ -312,8 +313,8 @@ public class BundleStatusProvider extends ComponentProviderAdapter { private void doClean() { int[] selectedModelRows = getSelectedModelRows(); boolean anythingCleaned = false; - for (BundleStatus o : bundleStatusModel.getRowObjects(selectedModelRows)) { - anythingCleaned |= bundleHost.getGhidraBundle(o.getPath()).clean(); + for (BundleStatus bs : bundleStatusTableModel.getRowObjects(selectedModelRows)) { + anythingCleaned |= bundleHost.getExistingGhidraBundle(bs.getPath()).clean(); } if (anythingCleaned) { AnimationUtils.shakeComponent(getComponent()); @@ -326,7 +327,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { return; } - bundleStatusModel.remove(selectedModelRows); + bundleStatusTableModel.remove(selectedModelRows); bundleStatusTable.clearSelection(); } @@ -369,7 +370,10 @@ public class BundleStatusProvider extends ComponentProviderAdapter { if (!files.isEmpty()) { Preferences.setProperty(preferenceForLastSelectedBundle, files.get(0).getAbsolutePath()); - bundleStatusModel.addNewPaths(files, true, false); + + bundleHost.addGhidraBundles( + files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()), + true, false); } } @@ -386,9 +390,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter { bundleStatusTable.chill(); List gbs = - bundleStatusModel.getRowObjects(selectedModelRows).stream().filter( + bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter( bs -> !bs.isActive()).map( - bs -> bundleHost.getGhidraBundle(bs.getPath())).collect( + bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect( Collectors.toList()); int total = gbs.size(); int total_activated = 0; @@ -435,9 +439,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter { long startTime = System.nanoTime(); List gbs = - bundleStatusModel.getRowObjects(selectedModelRows).stream().filter( + bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter( bs -> bs.isActive()).map( - bs -> bundleHost.getGhidraBundle(bs.getPath())).collect( + bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect( Collectors.toList()); int total = gbs.size(); @@ -470,7 +474,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { @Override public void run(TaskMonitor monitor) throws CancelledException { try { - GhidraBundle sb = bundleHost.getGhidraBundle(status.getPath()); + GhidraBundle sb = bundleHost.getExistingGhidraBundle(status.getPath()); if (activate) { sb.build(console.getStdErr()); bundleHost.activateSynchronously(sb.getBundleLoc()); @@ -496,14 +500,14 @@ public class BundleStatusProvider extends ComponentProviderAdapter { }, null, 1000); } - public BundleStatusModel getModel() { - return bundleStatusModel; + public BundleStatusTableModel getModel() { + return bundleStatusTableModel; } public void notifyTableRowChanged(BundleStatus status) { - int modelRowIndex = bundleStatusModel.getRowIndex(status); + int modelRowIndex = bundleStatusTableModel.getRowIndex(status); int viewRowIndex = filterPanel.getViewRow(modelRowIndex); - bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel, viewRowIndex)); + bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex)); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java similarity index 59% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusModel.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java index 9b41abba44..fbd413e385 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java @@ -20,17 +20,12 @@ import java.io.File; import java.util.*; import java.util.stream.Collectors; -import org.osgi.framework.Bundle; - import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.TableSortingContext; import generic.jar.ResourceFile; -import ghidra.app.script.GhidraScriptUtil; -import ghidra.framework.options.SaveState; -import ghidra.framework.preferences.Preferences; import ghidra.util.Msg; -public class BundleStatusModel extends AbstractSortedTableModel { +public class BundleStatusTableModel extends AbstractSortedTableModel { List columns = new ArrayList<>(); class Column { @@ -72,8 +67,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { @Override void setValue(BundleStatus status, Object newValue) { - status.setEnabled((Boolean) newValue); - fireBundleEnablementChanged(status, (Boolean) newValue); + fireBundleEnablementChangeRequested(status, (Boolean) newValue); } }; Column activeColumn = new Column("Active", Boolean.class) { @@ -90,7 +84,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { @Override void setValue(BundleStatus status, Object newValue) { status.setActive((Boolean) newValue); - fireBundleActivationChanged(status, (Boolean) newValue); + fireBundleActivationChangeRequested(status, (Boolean) newValue); } }; Column typeColumn = new Column("Type", String.class) { @@ -126,19 +120,28 @@ public class BundleStatusModel extends AbstractSortedTableModel { return badColumn; } - private BundleStatusProvider provider; + private BundleStatusComponentProvider provider; private List statuses; private BundleHost bundleHost; - OSGiListener bundleListener; + BundleHostListener bundleListener; private Map loc2status = new HashMap<>(); - BundleStatus getStatus(String bundleLocation) { - return loc2status.get(bundleLocation); + BundleStatus getStatus(GhidraBundle gb) { + BundleStatus status = loc2status.get(gb.getBundleLoc()); + if (status == null) { + Msg.showError(BundleStatusTableModel.this, provider.getComponent(), "bundle status error", + "bundle has no status!"); + } + return status; } public String getBundleLoc(BundleStatus status) { - return bundleHost.getGhidraBundle(status.getPath()).getBundleLoc(); + GhidraBundle gb = bundleHost.getExistingGhidraBundle(status.getPath()); + if (gb != null) { + return gb.getBundleLoc(); + } + return null; } /** @@ -154,56 +157,75 @@ public class BundleStatusModel extends AbstractSortedTableModel { } } - BundleStatusModel(BundleStatusProvider provider, BundleHost bundleHost) { + BundleStatusTableModel(BundleStatusComponentProvider provider, BundleHost bundleHost) { super(); this.provider = provider; this.bundleHost = bundleHost; + statuses = new ArrayList<>(); - // add unmodifiable paths - this.statuses = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map( - f -> new BundleStatus(f, true, true)).collect(Collectors.toList()); - // add user path - this.statuses.add(0, - new BundleStatus(GhidraScriptUtil.getUserScriptDirectory(), true, false)); - - computeCache(); - - bundleHost.addListener(bundleListener = new OSGiListener() { + bundleHost.addListener(bundleListener = new BundleHostListener() { @Override - public void bundleBuilt(GhidraBundle sb) { - BundleStatus bp = getStatus(sb.getBundleLoc()); - if (bp != null) { - bp.setSummary(sb.getSummary()); - int row = getRowIndex(bp); - fireTableRowsUpdated(row, row); - } + public void bundleBuilt(GhidraBundle gb) { + BundleStatus status = getStatus(gb); + status.setSummary(gb.getSummary()); + int row = getRowIndex(status); + fireTableRowsUpdated(row, row); } @Override - public void bundleActivationChange(Bundle b, boolean newActivation) { - BundleStatus status = getStatus(b.getLocation()); - if (status == null) { - Msg.showError(BundleStatusModel.this, provider.getComponent(), - "bundle status error", "bundle has no status!"); - } + public void bundleActivationChange(GhidraBundle gb, boolean newActivation) { + BundleStatus status = getStatus(gb); + int row = getRowIndex(status); if (newActivation) { - GhidraBundle gb = bundleHost.getGhidraBundle(status.getPath()); status.setActive(true); status.setSummary(gb.getSummary()); - int row = getRowIndex(status); - fireTableRowsUpdated(row, row); } else { status.setActive(false); status.setSummary(""); - int row = getRowIndex(status); - fireTableRowsUpdated(row, row); } - + fireTableRowsUpdated(row, row); } - }); - // fireTableDataChanged(); // this is done by the addNewPaths during restoreState + @Override + public void bundleAdded(GhidraBundle gb) { + addNewStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle()); + } + + @Override + public void bundlesAdded(Collection gbundles) { + int index = statuses.size(); + for (GhidraBundle gb : gbundles) { + BundleStatus status = + new BundleStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle()); + addStatusNoFire(status); + } + fireTableRowsInserted(index, gbundles.size() - 1); + } + + @Override + public void bundleRemoved(GhidraBundle gbundle) { + BundleStatus status = getStatus(gbundle); + removeStatus(status); + } + + @Override + public void bundlesRemoved(Collection gbundles) { + List toRemove = + gbundles.stream().map(BundleStatusTableModel.this::getStatus).collect( + Collectors.toUnmodifiableList()); + removeStatuses(toRemove); + } + + @Override + public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablement) { + BundleStatus status = getStatus(gbundle); + status.setEnabled(newEnablement); + int row = getRowIndex(status); + fireTableRowsUpdated(row, row); + } + + }); } @Override @@ -222,7 +244,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { return list; } - private void addStatus(BundleStatus path) { + private void addStatusNoFire(BundleStatus path) { if (statuses.contains(path)) { return; } @@ -239,7 +261,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { private BundleStatus addNewStatus(ResourceFile path, boolean enabled, boolean readonly) { BundleStatus p = new BundleStatus(path, enabled, readonly); int index = statuses.size(); - addStatus(p); + addStatusNoFire(p); fireTableRowsInserted(index, index); return p; } @@ -251,32 +273,44 @@ public class BundleStatusModel extends AbstractSortedTableModel { * @param enabled mark them all as enabled * @param readonly mark them all as readonly */ - void addNewPaths(List files, boolean enabled, boolean readonly) { + void addNewStatuses(List files, boolean enabled, boolean readonly) { int index = statuses.size(); for (File f : files) { BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly); - addStatus(status); + addStatusNoFire(status); } - fireBundlesChanged(); fireTableRowsInserted(index, files.size() - 1); } + private int removeStatusNoFire(BundleStatus status) { + if (!status.isReadOnly()) { + int i = statuses.indexOf(status); + loc2status.remove(getBundleLoc(status)); + return i; + } + Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path", + "System path cannot be removed: " + status.getPath().toString()); + return -1; + } + + void removeStatus(BundleStatus status) { + int row = removeStatusNoFire(status); + if (row >= 0) { + fireTableRowsDeleted(row, row); + } + } + void remove(int[] modelRows) { List toRemove = Arrays.stream(modelRows).mapToObj(statuses::get).collect( Collectors.toUnmodifiableList()); + removeStatuses(toRemove); + } + void removeStatuses(List toRemove) { for (BundleStatus status : toRemove) { - if (!status.isReadOnly()) { - statuses.remove(status); - loc2status.remove(getBundleLoc(status)); - } - else { - Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path", - "System path cannot be removed: " + status.getPath().toString()); - } + removeStatusNoFire(status); } fireTableDataChanged(); - fireBundlesChanged(); } /***************************************************/ @@ -327,7 +361,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { @Override public String getName() { - return BundleStatusModel.class.getSimpleName(); + return BundleStatusTableModel.class.getSimpleName(); } @Override @@ -335,31 +369,6 @@ public class BundleStatusModel extends AbstractSortedTableModel { return statuses; } - /** - * (add and) enable a path - * @param file path to enable - * @return true if the path is new - */ - public boolean enablePath(ResourceFile file) { - ResourceFile dir = file.isDirectory() ? file : file.getParentFile(); - for (BundleStatus path : statuses) { - if (path.getPath().equals(dir)) { - if (!path.isEnabled()) { - path.setEnabled(true); - fireTableDataChanged(); - fireBundlesChanged(); - return true; - } - return false; - } - } - addNewStatus(dir, true, false); - Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle, - dir.getAbsolutePath()); - fireBundlesChanged(); - return true; - } - /** * Test whether the given bundle is managed and not marked readonly * @param bundle the path to test @@ -385,9 +394,9 @@ public class BundleStatusModel extends AbstractSortedTableModel { fireTableDataChanged(); } - private ArrayList bundleStatusListeners = new ArrayList<>(); + private ArrayList bundleStatusListeners = new ArrayList<>(); - public void addListener(BundleStatusListener listener) { + public void addListener(BundleStatusChangeRequestListener listener) { synchronized (bundleStatusListeners) { if (!bundleStatusListeners.contains(listener)) { bundleStatusListeners.add(listener); @@ -395,100 +404,28 @@ public class BundleStatusModel extends AbstractSortedTableModel { } } - public void removeListener(BundleStatusListener listener) { + public void removeListener(BundleStatusChangeRequestListener listener) { synchronized (bundleStatusListeners) { bundleStatusListeners.remove(listener); } } - private void fireBundlesChanged() { + void fireBundleEnablementChangeRequested(BundleStatus path, boolean newValue) { synchronized (bundleStatusListeners) { - for (BundleStatusListener listener : bundleStatusListeners) { - listener.bundlesChanged(); + for (BundleStatusChangeRequestListener listener : bundleStatusListeners) { + listener.bundleEnablementChangeRequest(path, newValue); } } } - void fireBundleEnablementChanged(BundleStatus path, boolean newValue) { + void fireBundleActivationChangeRequested(BundleStatus path, boolean newValue) { synchronized (bundleStatusListeners) { - for (BundleStatusListener listener : bundleStatusListeners) { - listener.bundleEnablementChanged(path, newValue); + for (BundleStatusChangeRequestListener listener : bundleStatusListeners) { + listener.bundleActivationChangeRequest(path, newValue); } } } - void fireBundleActivationChanged(BundleStatus path, boolean newValue) { - synchronized (bundleStatusListeners) { - for (BundleStatusListener listener : bundleStatusListeners) { - listener.bundleActivationChanged(path, newValue); - } - } - } - - /** - * Restores the statuses from the specified SaveState object. - * @param ss the SaveState object - */ - public void restoreState(SaveState ss) { - String[] pathArr = ss.getStrings("BundleStatus_PATH", new String[0]); - - if (pathArr.length == 0) { - return; - } - - boolean[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]); - boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]); - - List currentPaths = new ArrayList<>(statuses); - statuses.clear(); - - for (int i = 0; i < pathArr.length; i++) { - BundleStatus currentStatus = getStatus(pathArr[i], currentPaths); - if (currentStatus != null) { - currentPaths.remove(currentStatus); - addStatus(new BundleStatus(pathArr[i], enableArr[i], readonlyArr[i])); - } - else if (!readonlyArr[i]) { - // skip read-only statuses which are not present in the current config - // This is needed to thin-out old default entries - addStatus(new BundleStatus(pathArr[i], enableArr[i], readonlyArr[i])); - } - } - fireTableDataChanged(); - fireBundlesChanged(); - } - - private static BundleStatus getStatus(String filepath, List statuses) { - for (BundleStatus status : statuses) { - if (filepath.equals(status.getPathAsString())) { - return status; - } - } - return null; - } - - /** - * Saves the statuses to the specified SaveState object. - * @param ss the SaveState object - */ - public void saveState(SaveState ss) { - String[] pathArr = new String[statuses.size()]; - boolean[] enableArr = new boolean[statuses.size()]; - boolean[] readonlyArr = new boolean[statuses.size()]; - - int index = 0; - for (BundleStatus status : statuses) { - pathArr[index] = status.getPathAsString(); - enableArr[index] = status.isEnabled(); - readonlyArr[index] = status.isReadOnly(); - ++index; - } - - ss.putStrings("BundleStatus_PATH", pathArr); - ss.putBooleans("BundleStatus_ENABLE", enableArr); - ss.putBooleans("BundleStatus_READ", readonlyArr); - } - public List getRowObjects(int[] modelRowIndices) { List rows = new ArrayList<>(modelRowIndices.length); for (int i : modelRowIndices) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java index 851d462aa8..480e3ae0ef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java @@ -359,7 +359,7 @@ public class GhidraSourceBundle extends GhidraBundle { int failing = getFailingSourcesCount(); int newSourcecount = getNewSourcesCount(); - long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel + long lastBundleActivation = 0; // XXX record last bundle activation in bundlehost if (failing > 0 && (lastBundleActivation > getLastCompileAttempt())) { needsCompile = true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiListener.java deleted file mode 100644 index 50546656d3..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package ghidra.app.plugin.core.osgi; - -import org.osgi.framework.Bundle; - -/** - * Listener for OSGi framework events. - */ -public interface OSGiListener { - - default void bundleBuilt(GhidraBundle gb) { - // - } - - default void bundleActivationChange(Bundle b, boolean newActivation) { - // - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java index 8e8b6eee1b..4d338e07a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java @@ -20,6 +20,8 @@ import java.awt.datatransfer.Transferable; import java.awt.dnd.*; import java.util.ArrayList; +import javax.swing.table.TableModel; + import docking.dnd.*; import generic.jar.ResourceFile; import ghidra.util.table.GhidraTable; @@ -33,9 +35,10 @@ public class DraggableScriptTable extends GhidraTable implements Draggable { /** * Constructs a new DraggableGhidraTable. * @param provider the provider, from which getTableModel and getScriptAt are used + * @param model provider's table model */ - public DraggableScriptTable(GhidraScriptComponentProvider provider) { - super(provider.getTableModel()); + public DraggableScriptTable(GhidraScriptComponentProvider provider, TableModel model) { + super(model); this.provider = provider; initDragNDrop(); @@ -96,6 +99,7 @@ public class DraggableScriptTable extends GhidraTable implements Draggable { */ @Override public void move() { + // } /** @@ -104,6 +108,7 @@ public class DraggableScriptTable extends GhidraTable implements Draggable { */ @Override public void dragCanceled(DragSourceDropEvent event) { + // } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java index b66abd913a..941121f48d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java @@ -52,7 +52,7 @@ class GhidraScriptActionManager { private GhidraScriptComponentProvider provider; private GhidraScriptMgrPlugin plugin; private DockingAction refreshAction; - private DockingAction bundlePathsAction; + private DockingAction bundleStatusAction; private DockingAction newAction; private DockingAction runAction; private DockingAction runLastAction; @@ -351,7 +351,7 @@ class GhidraScriptActionManager { refreshAction.setEnabled(true); plugin.getTool().addLocalAction(provider, refreshAction); - bundlePathsAction = new DockingAction("Bundle Status", plugin.getName()) { + bundleStatusAction = new DockingAction("Bundle Status", plugin.getName()) { @Override public void actionPerformed(ActionContext context) { provider.showBundleStatusComponent(); @@ -363,14 +363,14 @@ class GhidraScriptActionManager { return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile); } }; - bundlePathsAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" }, + bundleStatusAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" }, ResourceManager.loadImage("images/text_list_bullets.png"), null)); - bundlePathsAction.setToolBarData( + bundleStatusAction.setToolBarData( new ToolBarData(ResourceManager.loadImage("images/text_list_bullets.png"), null)); - bundlePathsAction.setDescription("Bundle Status"); - bundlePathsAction.setEnabled(true); - plugin.getTool().addLocalAction(provider, bundlePathsAction); + bundleStatusAction.setDescription("Bundle Status"); + bundleStatusAction.setEnabled(true); + plugin.getTool().addLocalAction(provider, bundleStatusAction); helpAction = new DockingAction("Ghidra API Help", plugin.getName()) { @Override @@ -439,7 +439,7 @@ class GhidraScriptActionManager { } HelpLocation getPathHelpLocation() { - return new HelpLocation(plugin.getName(), bundlePathsAction.getName()); + return new HelpLocation(plugin.getName(), bundleStatusAction.getName()); } HelpLocation getKeyBindingHelpLocation() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index b9c5486cbf..803e4f1080 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -23,7 +23,8 @@ import java.util.*; import java.util.stream.Collectors; import javax.swing.*; -import javax.swing.table.*; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; import javax.swing.text.html.HTMLEditorKit; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; @@ -71,7 +72,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { private GTree scriptCategoryTree; private DraggableScriptTable scriptTable; private GhidraScriptTableModel tableModel; - private BundleStatusProvider bundleStatusProvider; + private BundleStatusComponentProvider bundleStatusComponentProvider; private TaskListener taskListener = new ScriptTaskListener(); private GhidraScriptActionManager actionManager; private GhidraTableFilterPanel tableFilterPanel; @@ -97,28 +98,199 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } }; + final private BundleHost bundleHost; + final private RefreshingBundleHostListener refreshingBundleHostListener = + new RefreshingBundleHostListener(); + GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) { super(plugin.getTool(), "Script Manager", plugin.getName()); + this.plugin = plugin; + bundleHost = new BundleHost(); + GhidraScriptUtil.initialize(bundleHost); + + // first, let the status component register its bundle host listener + bundleStatusComponentProvider = + new BundleStatusComponentProvider(plugin.getTool(), plugin.getName(), bundleHost); + // now add bundles + bundleHost.addGhidraBundle(GhidraScriptUtil.getUserScriptDirectory(), true, false); + bundleHost.addGhidraBundles(GhidraScriptUtil.getSystemScriptPaths(), true, true); + // finally, add the GUI listener for this component + bundleHost.addListener(refreshingBundleHostListener); + setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setIcon(ResourceManager.loadImage("images/play.png")); addToToolbar(); setWindowGroup(WINDOW_GROUP); build(); + plugin.getTool().addComponentProvider(this, false); actionManager = new GhidraScriptActionManager(this, plugin); updateTitle(); } + private void build() { + scriptRoot = new RootNode(); + + scriptCategoryTree = new GTree(scriptRoot); + scriptCategoryTree.setName("CATEGORY_TREE"); + scriptCategoryTree.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + maybeSelect(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + maybeSelect(e); + } + + private void maybeSelect(MouseEvent e) { + if (e.isPopupTrigger()) { + TreePath path = scriptCategoryTree.getPathForLocation(e.getX(), e.getY()); + scriptCategoryTree.setSelectionPath(path); + } + } + }); + scriptCategoryTree.addGTreeSelectionListener(e -> { + tableModel.fireTableDataChanged(); // trigger a refilter + TreePath path = e.getPath(); + if (path != null) { + scriptCategoryTree.expandPath(path); + } + }); + + scriptCategoryTree.getSelectionModel().setSelectionMode( + TreeSelectionModel.SINGLE_TREE_SELECTION); + + tableModel = new GhidraScriptTableModel(this); + + scriptTable = new DraggableScriptTable(this, tableModel); + scriptTable.setName("SCRIPT_TABLE"); + scriptTable.setAutoLookupColumn(tableModel.getNameColumnIndex()); + scriptTable.setRowSelectionAllowed(true); + scriptTable.setAutoCreateColumnsFromModel(false); + scriptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + scriptTable.getSelectionModel().addListSelectionListener(e -> { + if (e.getValueIsAdjusting()) { + return; + } + tool.contextChanged(GhidraScriptComponentProvider.this); + updateDescriptionPanel(); + }); + tableModel.addTableModelListener(e -> updateTitle()); + + scriptTable.addMouseListener(new GMouseListenerAdapter() { + @Override + public void popupTriggered(MouseEvent e) { + int displayRow = scriptTable.rowAtPoint(e.getPoint()); + if (displayRow >= 0) { + scriptTable.setRowSelectionInterval(displayRow, displayRow); + } + } + + @Override + public void doubleClickTriggered(MouseEvent e) { + runScript(); + } + }); + + TableColumnModel columnModel = scriptTable.getColumnModel(); + // Set default column sizes + for (int i = 0; i < columnModel.getColumnCount(); i++) { + TableColumn column = columnModel.getColumn(i); + String name = (String) column.getHeaderValue(); + switch (name) { + case GhidraScriptTableModel.SCRIPT_ACTION_COLUMN_NAME: + initializeUnresizableColumn(column, 50); + break; + case GhidraScriptTableModel.SCRIPT_STATUS_COLUMN_NAME: + initializeUnresizableColumn(column, 50); + break; + } + } + + JScrollPane scriptTableScroll = new JScrollPane(scriptTable); + buildFilter(); + + JPanel tablePanel = new JPanel(new BorderLayout()); + tablePanel.add(scriptTableScroll, BorderLayout.CENTER); + tablePanel.add(tableFilterPanel, BorderLayout.SOUTH); + + JSplitPane treeTableSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + treeTableSplit.setLeftComponent(scriptCategoryTree); + treeTableSplit.setRightComponent(tablePanel); + treeTableSplit.setDividerLocation(150); + + JComponent descriptionPanel = buildDescriptionComponent(); + + dataDescriptionSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); + dataDescriptionSplit.setName("dataDescriptionSplit"); + dataDescriptionSplit.setTopComponent(treeTableSplit); + dataDescriptionSplit.setBottomComponent(descriptionPanel); + + component = new JPanel(new BorderLayout()); + component.add(dataDescriptionSplit, BorderLayout.CENTER); + } + + //================================================================================================== + // Inner Classes + //================================================================================================== + + public void readConfigState(SaveState saveState) { + bundleHost.restoreState(saveState); + + actionManager.restoreUserDefinedKeybindings(saveState); + actionManager.restoreScriptsThatAreInTool(saveState); + + final int descriptionDividerLocation = saveState.getInt(DESCRIPTION_DIVIDER_LOCATION, 0); + if (descriptionDividerLocation > 0) { + + ComponentListener listener = new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); // give the top pane the most space + } + }; + component.addComponentListener(listener); + + dataDescriptionSplit.setDividerLocation(descriptionDividerLocation); + } + + String filterText = saveState.getString(FILTER_TEXT, ""); + tableFilterPanel.setFilterText(filterText); + } + + public void writeConfigState(SaveState saveState) { + bundleHost.saveState(saveState); + + actionManager.saveUserDefinedKeybindings(saveState); + actionManager.saveScriptsThatAreInTool(saveState); + + int dividerLocation = dataDescriptionSplit.getDividerLocation(); + if (dividerLocation > 0) { + saveState.putInt(DESCRIPTION_DIVIDER_LOCATION, dividerLocation); + } + + String filterText = tableFilterPanel.getFilterText(); + saveState.putString(FILTER_TEXT, filterText); + } + void dispose() { editorMap.clear(); - scriptCategoryTree.dispose(); + scriptCategoryTree.dispose(); scriptTable.dispose(); tableFilterPanel.dispose(); actionManager.dispose(); - bundleStatusProvider.dispose(); + bundleStatusComponentProvider.dispose(); + GhidraScriptUtil.dispose(); + } + + public BundleHost getBundleHost() { + return bundleHost; } GhidraScriptActionManager getActionManager() { @@ -130,14 +302,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } void showBundleStatusComponent() { - bundleStatusProvider.setVisible(true); - } - - private void performRefresh() { - GhidraScriptUtil.getBundleHost().setBundlePaths( - bundleStatusProvider.getModel().getEnabledPaths()); - GhidraScriptUtil.clearMetadata(); - refresh(); + bundleStatusComponentProvider.setVisible(true); } void assignKeyBinding() { @@ -161,7 +326,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { void renameScript() { ResourceFile script = getSelectedScript(); ResourceFile directory = script.getParentFile(); - if (!bundleStatusProvider.getModel().isWriteable(directory)) { + if (!bundleStatusComponentProvider.getModel().isWriteable(directory)) { Msg.showWarn(getClass(), getComponent(), getName(), "Unable to rename scripts in '" + directory + "'."); return; @@ -272,10 +437,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } - TableModel getTableModel() { - return tableModel; - } - JTable getTable() { return scriptTable; } @@ -284,17 +445,17 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return tableFilterPanel.getViewRow(tableModel.getScriptIndex(scriptFile)); } - ResourceFile getScriptAt(int rowIndex) { - return tableModel.getScriptAt(tableFilterPanel.getModelRow(rowIndex)); + ResourceFile getScriptAt(int viewRowIndex) { + return tableModel.getScriptAt(tableFilterPanel.getModelRow(viewRowIndex)); } public List getScriptDirectories() { - return bundleStatusProvider.getModel().getEnabledPaths().stream().filter( + return bundleStatusComponentProvider.getModel().getEnabledPaths().stream().filter( ResourceFile::isDirectory).collect(Collectors.toList()); } public List getWritableScriptDirectories() { - BundleStatusModel m = bundleStatusProvider.getModel(); + BundleStatusTableModel m = bundleStatusComponentProvider.getModel(); return m.getEnabledPaths().stream().filter(ResourceFile::isDirectory).filter( m::isWriteable).collect(Collectors.toList()); } @@ -311,7 +472,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } ResourceFile directory = script.getParentFile(); - if (!bundleStatusProvider.getModel().isWriteable(directory)) { + if (!bundleStatusComponentProvider.getModel().isWriteable(directory)) { Msg.showWarn(getClass(), getComponent(), getName(), "Unable to delete scripts in '" + directory + "'."); return; @@ -353,11 +514,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } public void enableScriptDirectory(ResourceFile scriptDir) { - if (bundleStatusProvider.getModel().enablePath(scriptDir)) { - Msg.showInfo(this, getComponent(), "Script Path Added/Enabled", - "The directory has been automatically enabled for use:\n" + - scriptDir.getAbsolutePath()); - } + bundleHost.enablePath(scriptDir); + Msg.showInfo(this, getComponent(), "Script Path Added/Enabled", + "The directory has been automatically enabled for use:\n" + + scriptDir.getAbsolutePath()); } void newScript() { @@ -410,12 +570,13 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } void runScript(String scriptName, TaskListener listener) { - List dirPaths = bundleStatusProvider.getModel().getEnabledPaths(); - for (ResourceFile dir : dirPaths) { - ResourceFile scriptSource = new ResourceFile(dir, scriptName); - if (scriptSource.exists()) { - runScript(scriptSource, listener); - return; + for (ResourceFile dir : bundleHost.getBundlePaths()) { + if (dir.isDirectory()) { + ResourceFile scriptSource = new ResourceFile(dir, scriptName); + if (scriptSource.exists()) { + runScript(scriptSource, listener); + return; + } } } throw new IllegalArgumentException("Script does not exist: " + scriptName); @@ -530,6 +691,60 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return categoryPath; } + class RefreshingBundleHostListener implements BundleHostListener { + + @Override + public void bundleBuilt(GhidraBundle sb) { + if (sb instanceof GhidraSourceBundle) { + GhidraSourceBundle gsb = (GhidraSourceBundle) sb; + for (ResourceFile sf : gsb.getNewSources()) { + if (GhidraScriptUtil.containsMetadata(sf)) { + ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(sf); + BuildFailure e = gsb.getErrors(sf); + info.setCompileErrors(e != null); + } + } + tableModel.fireTableDataChanged(); + } + } + + @Override + public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablment) { + if (gbundle instanceof GhidraSourceBundle) { + performRefresh(); + } + } + + @Override + public void bundleAdded(GhidraBundle gbundle) { + plugin.getTool().setConfigChanged(true); + performRefresh(); + } + + @Override + public void bundlesAdded(Collection gbundles) { + plugin.getTool().setConfigChanged(true); + performRefresh(); + } + + @Override + public void bundleRemoved(GhidraBundle gbundle) { + plugin.getTool().setConfigChanged(true); + performRefresh(); + } + + @Override + public void bundlesRemoved(Collection gbundles) { + plugin.getTool().setConfigChanged(true); + performRefresh(); + } + } + + private void performRefresh() { + GhidraScriptUtil.clearMetadata(); + refresh(); + } + void refresh() { hasBeenRefreshed = true; @@ -753,147 +968,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return true; } - private void build() { - bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName()); - - // XXX remember listener to remove later - bundleStatusProvider.getModel().addListener(new BundleStatusListener() { - @Override - public void bundlesChanged() { - plugin.getTool().setConfigChanged(true); - performRefresh(); - } - - @Override - public void bundleEnablementChanged(BundleStatus status, boolean enabled) { - if (status.isDirectory()) { - performRefresh(); - } - } - }); - - BundleHost.getInstance().addListener(new OSGiListener() { - - @Override - public void bundleBuilt(GhidraBundle sb) { - if (sb instanceof GhidraSourceBundle) { - GhidraSourceBundle gsb = (GhidraSourceBundle) sb; - for (ResourceFile sf : gsb.getNewSources()) { - if (GhidraScriptUtil.containsMetadata(sf)) { - ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(sf); - BuildFailure e = gsb.getErrors(sf); - info.setCompileErrors(e != null); - } - } - tableModel.fireTableDataChanged(); - } - } - }); - - scriptRoot = new RootNode(); - - scriptCategoryTree = new GTree(scriptRoot); - scriptCategoryTree.setName("CATEGORY_TREE"); - scriptCategoryTree.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - maybeSelect(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - maybeSelect(e); - } - - private void maybeSelect(MouseEvent e) { - if (e.isPopupTrigger()) { - TreePath path = scriptCategoryTree.getPathForLocation(e.getX(), e.getY()); - scriptCategoryTree.setSelectionPath(path); - } - } - }); - scriptCategoryTree.addGTreeSelectionListener(e -> { - tableModel.fireTableDataChanged(); // trigger a refilter - TreePath path = e.getPath(); - if (path != null) { - scriptCategoryTree.expandPath(path); - } - }); - - scriptCategoryTree.getSelectionModel().setSelectionMode( - TreeSelectionModel.SINGLE_TREE_SELECTION); - - tableModel = new GhidraScriptTableModel(this); - - scriptTable = new DraggableScriptTable(this); - scriptTable.setName("SCRIPT_TABLE"); - scriptTable.setAutoLookupColumn(tableModel.getNameColumnIndex()); - scriptTable.setRowSelectionAllowed(true); - scriptTable.setAutoCreateColumnsFromModel(false); - scriptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - scriptTable.getSelectionModel().addListSelectionListener(e -> { - if (e.getValueIsAdjusting()) { - return; - } - tool.contextChanged(GhidraScriptComponentProvider.this); - updateDescriptionPanel(); - }); - tableModel.addTableModelListener(e -> updateTitle()); - - scriptTable.addMouseListener(new GMouseListenerAdapter() { - @Override - public void popupTriggered(MouseEvent e) { - int displayRow = scriptTable.rowAtPoint(e.getPoint()); - if (displayRow >= 0) { - scriptTable.setRowSelectionInterval(displayRow, displayRow); - } - } - - @Override - public void doubleClickTriggered(MouseEvent e) { - runScript(); - } - }); - - TableColumnModel columnModel = scriptTable.getColumnModel(); - // Set default column sizes - for (int i = 0; i < columnModel.getColumnCount(); i++) { - TableColumn column = columnModel.getColumn(i); - String name = (String) column.getHeaderValue(); - switch (name) { - case GhidraScriptTableModel.SCRIPT_ACTION_COLUMN_NAME: - initializeUnresizableColumn(column, 50); - break; - case GhidraScriptTableModel.SCRIPT_STATUS_COLUMN_NAME: - initializeUnresizableColumn(column, 50); - break; - } - } - - JScrollPane scriptTableScroll = new JScrollPane(scriptTable); - buildFilter(); - - JPanel tablePanel = new JPanel(new BorderLayout()); - tablePanel.add(scriptTableScroll, BorderLayout.CENTER); - tablePanel.add(tableFilterPanel, BorderLayout.SOUTH); - - JSplitPane treeTableSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); - treeTableSplit.setLeftComponent(scriptCategoryTree); - treeTableSplit.setRightComponent(tablePanel); - treeTableSplit.setDividerLocation(150); - - JComponent descriptionPanel = buildDescriptionComponent(); - - dataDescriptionSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); - dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); - dataDescriptionSplit.setName("dataDescriptionSplit"); - dataDescriptionSplit.setTopComponent(treeTableSplit); - dataDescriptionSplit.setBottomComponent(descriptionPanel); - - component = new JPanel(new BorderLayout()); - component.add(dataDescriptionSplit, BorderLayout.CENTER); - } - private void initializeUnresizableColumn(TableColumn column, int width) { column.setPreferredWidth(width); column.setMinWidth(width); @@ -1058,47 +1132,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } - public void readConfigState(SaveState saveState) { - bundleStatusProvider.getModel().restoreState(saveState); - - // pull in the just-loaded paths - List paths = bundleStatusProvider.getModel().getEnabledPaths(); - GhidraScriptUtil.initializeScriptBundlePaths(paths); - actionManager.restoreUserDefinedKeybindings(saveState); - actionManager.restoreScriptsThatAreInTool(saveState); - - final int descriptionDividerLocation = saveState.getInt(DESCRIPTION_DIVIDER_LOCATION, 0); - if (descriptionDividerLocation > 0) { - - ComponentListener listener = new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); // give the top pane the most space - } - }; - component.addComponentListener(listener); - - dataDescriptionSplit.setDividerLocation(descriptionDividerLocation); - } - - String filterText = saveState.getString(FILTER_TEXT, ""); - tableFilterPanel.setFilterText(filterText); - } - - public void writeConfigState(SaveState saveState) { - bundleStatusProvider.getModel().saveState(saveState); - actionManager.saveUserDefinedKeybindings(saveState); - actionManager.saveScriptsThatAreInTool(saveState); - - int dividerLocation = dataDescriptionSplit.getDividerLocation(); - if (dividerLocation > 0) { - saveState.putInt(DESCRIPTION_DIVIDER_LOCATION, dividerLocation); - } - - String filterText = tableFilterPanel.getFilterText(); - saveState.putString(FILTER_TEXT, filterText); - } - /********************************************************************/ @Override @@ -1150,10 +1183,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } -//================================================================================================== -// Inner Classes -//================================================================================================== - /** Table filter that uses the state of the tree to further filter */ private class ScriptTableSecondaryFilter implements TableFilter { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index 2893402a7b..f3797b948a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import generic.jar.ResourceFile; import ghidra.app.plugin.core.osgi.BundleHost; +import ghidra.app.plugin.core.osgi.OSGiException; import ghidra.framework.Application; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -47,18 +48,51 @@ public class GhidraScriptUtil { private static Map> scriptNameToFilesMap = new HashMap<>(); - static BundleHost _bundleHost = BundleHost.getInstance(); + static BundleHost _bundleHost; public static BundleHost getBundleHost() { return _bundleHost; } + public static void initialize(BundleHost bundleHost) { + if (_bundleHost != null) { + throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); + } + + try { + _bundleHost = bundleHost; + _bundleHost.startFelix(); + } + catch (OSGiException | IOException e) { + e.printStackTrace(); + Msg.error(GhidraScript.class, "failed to initialize BundleHost", e); + } + } + /** - * Sets the script bundle paths - * @param newPaths the new script bundle paths + * initialization for headless runs. + * + * @param bundleHost the host to use + * @param paths bundle paths added as system bundles + * */ - public static void initializeScriptBundlePaths(List newPaths) { - _bundleHost.setBundlePaths(new ArrayList<>(newPaths)); + public static void initialize(BundleHost bundleHost, List paths) { + initialize(bundleHost); + _bundleHost.addGhidraBundles(paths, true, true); + } + + public static void dispose() { + clearMetadata(); + _bundleHost = null; + providers = null; + } + + /** + * clear ScriptInfo metadata cached by GhidraScriptUtil + */ + public static void clearMetadata() { + scriptFileToInfoMap.clear(); + scriptNameToFilesMap.clear(); } /** @@ -201,14 +235,6 @@ public class GhidraScriptUtil { } } - /** - * clear ScriptInfo metadata cached by GhidraScriptUtil - */ - public static void clearMetadata() { - scriptFileToInfoMap.clear(); // clear our cache of old files - scriptNameToFilesMap.clear(); - } - /** * Returns the base name give a script file. * For example, given "C:\Temp\SomeClass.java", @@ -437,7 +463,7 @@ public class GhidraScriptUtil { } /** Returns true if the given filename exists in any of the given directories */ - private static ResourceFile findScriptFileInPaths(List scriptDirectories, + private static ResourceFile findScriptFileInPaths(Collection scriptDirectories, String filename) { String validatedName = fixupName(filename); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index b459b8fbf6..5ee845bc8d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -35,7 +35,7 @@ public class JavaScriptProvider extends GhidraScriptProvider { if (sourceDir == null) { return null; } - return (GhidraSourceBundle) _bundleHost.getGhidraBundle(sourceDir); + return (GhidraSourceBundle) _bundleHost.getExistingGhidraBundle(sourceDir); } @Override @@ -67,6 +67,7 @@ public class JavaScriptProvider extends GhidraScriptProvider { @Override public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + // in headless operation, ScriptInfo objects can be created here ScriptInfo info = GhidraScriptUtil.getScriptInfo(sourceFile); try { Class clazz = loadClass(sourceFile, writer); 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 87392a4f12..b883904942 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 @@ -22,6 +22,7 @@ import java.util.List; import generic.jar.ResourceFile; import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; +import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.*; import ghidra.framework.Application; import ghidra.framework.HeadlessGhidraApplicationConfiguration; @@ -204,7 +205,7 @@ public class GhidraScriptRunner implements GhidraLaunchable { paths.addAll(GhidraScriptUtil.getSystemScriptPaths()); paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } - GhidraScriptUtil.initializeScriptBundlePaths(paths); + GhidraScriptUtil.initialize(new BundleHost(), paths); StringBuffer buf = new StringBuffer("HEADLESS Script Paths:"); for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) { 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 a51c001778..5e5e651468 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 @@ -26,6 +26,7 @@ import generic.util.Path; import ghidra.GhidraApplicationLayout; import ghidra.GhidraJarApplicationLayout; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.*; import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption; import ghidra.app.util.importer.AutoImporter; @@ -673,7 +674,7 @@ public class HeadlessAnalyzer { paths.addAll(GhidraScriptUtil.getSystemScriptPaths()); paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } - GhidraScriptUtil.initializeScriptBundlePaths(paths); + GhidraScriptUtil.initialize(new BundleHost(), paths); StringBuffer buf = new StringBuffer("HEADLESS Script Paths:"); for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index 3b3100c4a5..5f4b846bb4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -551,6 +551,12 @@ public class TestEnv { } public ScriptTaskListener runScript(File script) throws PluginException { + GhidraScriptMgrPlugin sm = getPlugin(GhidraScriptMgrPlugin.class); + if (sm == null) { + lazyTool().addPlugin(GhidraScriptMgrPlugin.class.getName()); + sm = getPlugin(GhidraScriptMgrPlugin.class); + } + JavaScriptProvider scriptProvider = new JavaScriptProvider(); PrintWriter writer = new PrintWriter(System.out); ResourceFile resourceFile = new ResourceFile(script); @@ -567,11 +573,6 @@ public class TestEnv { throw new RuntimeException("Failed to compile script " + script.getAbsolutePath()); } - GhidraScriptMgrPlugin sm = getPlugin(GhidraScriptMgrPlugin.class); - if (sm == null) { - lazyTool().addPlugin(GhidraScriptMgrPlugin.class.getName()); - sm = getPlugin(GhidraScriptMgrPlugin.class); - } String scriptName = script.getName(); ScriptTaskListener listener = new ScriptTaskListener(scriptName); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 6afa942ebd..2dab082b00 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -47,7 +47,8 @@ import generic.jar.ResourceFile; import generic.test.TestUtils; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.console.ConsoleComponentProvider; -import ghidra.app.plugin.core.osgi.*; +import ghidra.app.plugin.core.osgi.BundleHost; +import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider; import ghidra.app.script.*; import ghidra.app.services.ConsoleService; import ghidra.framework.Application; @@ -86,6 +87,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest @Before public void setUp() throws Exception { + System.err.printf("===== Starting %s ======\n", testName.getMethodName()); + setErrorGUIEnabled(false); // change the eclipse port so that Eclipse doesn't try to edit the script when @@ -130,7 +133,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest waitForSwing(); - System.err.printf("===== Starting %s ======\n", testName.getMethodName()); } protected Program buildProgram() throws Exception { @@ -167,8 +169,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest } wipeUserScripts(); - BundleHost.getInstance().removeAllListeners(); - env.dispose(); } @@ -632,12 +632,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest } protected void addScriptDirectory(ResourceFile scriptDir) { - provider.enableScriptDirectory(scriptDir); - - @SuppressWarnings("unchecked") - List scriptBundlePaths = - (List) getInstanceField("bundlePaths", GhidraScriptUtil.getBundleHost()); - scriptBundlePaths.add(scriptDir); + // when there is a provider, GhidraScriptUtil has the same BundleHost + provider.getBundleHost().enablePath(scriptDir); } protected String runScript(String scriptName) throws Exception { @@ -985,46 +981,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest } - /** - * rip out GUI listeners from bundle and OSGI components - * use as follows: - * - *
{@code
-	 *   try (var rewire = unwireGUI()) {
-	 *     // ... test code ...
-	 *   }
-	 * }
- * - * @return an undo closure which puts the listeners back in place - */ - @SuppressWarnings("unchecked") - protected AutoCloseable unwireGUI() { - waitForSwing(); - BundleStatusProvider bundleStatusProvider = - (BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider); - BundleStatusModel bundleStatusModel = bundleStatusProvider.getModel(); - - List bundleStatusListeners = - (List) TestUtils.getInstanceField("bundleStatusListeners", - bundleStatusModel); - - List storedBundleStatusListeners = - new ArrayList<>(bundleStatusListeners); - bundleStatusListeners.clear(); - - List osgiListeners = - (List) TestUtils.getInstanceField("osgiListeners", - BundleHost.getInstance()); - ArrayList storedOsgiListeners = new ArrayList<>(osgiListeners); - osgiListeners.clear(); - - return () -> { - waitForSwing(); - bundleStatusListeners.addAll(storedBundleStatusListeners); - osgiListeners.addAll(storedOsgiListeners); - }; - } - protected void cleanupOldTestFiles() throws IOException { // remove the compiled bundles directory so that any scripts we use will be recompiled wipe(BundleHost.getCompiledBundlesDir()); @@ -1032,9 +988,9 @@ public abstract class AbstractGhidraScriptMgrPluginTest String myTestName = super.testName.getMethodName(); // destroy any NewScriptxxx files...and Temp ones too - BundleStatusProvider bundleStatusProvider = - (BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider); - List paths = bundleStatusProvider.getModel().getEnabledPaths(); + BundleStatusComponentProvider bundleStatusComponentProvider = + (BundleStatusComponentProvider) TestUtils.getInstanceField("bundleStatusComponentProvider", provider); + List paths = bundleStatusComponentProvider.getModel().getEnabledPaths(); for (ResourceFile path : paths) { File file = path.getFile(false); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java index dde3b3e8aa..b436b1efb2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java @@ -61,15 +61,13 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes @Test public void testScriptWithInnerClassAndLocalClass() throws Exception { - try (var rewire = unwireGUI()) { - ResourceFile innerScriptFile = createInnerClassScript(); + ResourceFile innerScriptFile = createInnerClassScript(); - String output = runScriptAndGetOutput(innerScriptFile); + String output = runScriptAndGetOutput(innerScriptFile); - assertTrue("Inner class output not found", output.indexOf("I am an inner class") != -1); - assertTrue("External class output not found", - output.indexOf("I am an external class") != -1); - } + assertTrue("Inner class output not found", output.indexOf("I am an inner class") != -1); + assertTrue("External class output not found", + output.indexOf("I am an external class") != -1); } @Test @@ -78,52 +76,47 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // Test that if a user uses a parent class other than GhidraScript, that parent // class will get recompiled when it changes. // - try (var rewire = unwireGUI()) { - ResourceFile parentScriptFile = createTempScriptFile("AbstractParentScript"); + ResourceFile parentScriptFile = createTempScriptFile("AbstractParentScript"); - String v1Message = "Hello from version 1"; - writeAbstractScriptContents(parentScriptFile, v1Message); + String v1Message = "Hello from version 1"; + writeAbstractScriptContents(parentScriptFile, v1Message); - ResourceFile childScriptFile = createChildScript(parentScriptFile, null); + ResourceFile childScriptFile = createChildScript(parentScriptFile, null); - String output = runScriptAndGetOutput(childScriptFile); + String output = runScriptAndGetOutput(childScriptFile); - assertContainsText(v1Message, output); + assertContainsText(v1Message, output); - // change the parent script - String v2Message = "Hello from version 2"; - writeAbstractScriptContents(parentScriptFile, v2Message); + // change the parent script + String v2Message = "Hello from version 2"; + writeAbstractScriptContents(parentScriptFile, v2Message); - output = runScriptAndGetOutput(childScriptFile); + output = runScriptAndGetOutput(childScriptFile); - assertContainsText(v2Message, output); - } + assertContainsText(v2Message, output); } @Test public void testScriptWithParentInPackageRecompile() throws Exception { - try (var rewire = unwireGUI()) { + final String parentName = "ParentInPackageScript"; + final String packageName = parentName + "Pkg"; - final String parentName = "ParentInPackageScript"; - final String packageName = parentName + "Pkg"; + ResourceFile parentScriptFile = createTempScriptFile(parentName, packageName); + writePackageScriptContents(parentScriptFile, packageName); - ResourceFile parentScriptFile = createTempScriptFile(parentName, packageName); - writePackageScriptContents(parentScriptFile, packageName); + ResourceFile childScriptFile = createChildScript(parentScriptFile, packageName); - ResourceFile childScriptFile = createChildScript(parentScriptFile, packageName); + String output = runScriptAndGetOutput(childScriptFile); + assertContainsText("0", output); + output = runScriptAndGetOutput(childScriptFile); + assertContainsText("1", output); - String output = runScriptAndGetOutput(childScriptFile); - assertContainsText("0", output); - output = runScriptAndGetOutput(childScriptFile); - assertContainsText("1", output); - - // Change the parent script so it recompiles and resets its static state - Thread.sleep(1000); // Ensure our file write advances the last modified timestamp - writePackageScriptContents(parentScriptFile, packageName); - output = runScriptAndGetOutput(childScriptFile); - assertContainsText("0", output); - } + // Change the parent script so it recompiles and resets its static state + Thread.sleep(1000); // Ensure our file write advances the last modified timestamp + writePackageScriptContents(parentScriptFile, packageName); + output = runScriptAndGetOutput(childScriptFile); + assertContainsText("0", output); } @Test @@ -132,61 +125,58 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // Test that compiling a script to the user's script dir will use a bin dir for the // output. // - try (var rewire = unwireGUI()) { + // create a new dummy script + File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR); + String rawScriptName = testName.getMethodName(); + String scriptFilename = rawScriptName + ".java"; + File newScriptFile = new File(userScriptsDir, scriptFilename); + if (newScriptFile.exists()) { + assertTrue("Unable to delete script file for testing: " + newScriptFile, + newScriptFile.delete()); + } - // create a new dummy script - File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR); - String rawScriptName = testName.getMethodName(); - String scriptFilename = rawScriptName + ".java"; - File newScriptFile = new File(userScriptsDir, scriptFilename); - if (newScriptFile.exists()) { - assertTrue("Unable to delete script file for testing: " + newScriptFile, - newScriptFile.delete()); - } + JavaScriptProvider scriptProvider = new JavaScriptProvider(); + scriptProvider.createNewScript(new ResourceFile(newScriptFile), null); - JavaScriptProvider scriptProvider = new JavaScriptProvider(); - scriptProvider.createNewScript(new ResourceFile(newScriptFile), null); + // remove all class files from the user script dir (none should ever be there) + FileFilter classFileFilter = file -> file.getName().endsWith(".class"); + File[] userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); + for (File file : userScriptDirFiles) { + file.delete(); + } + userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); + boolean isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0; + assertTrue("Unable to delete class files from the user scripts directory", isEmpty); - // remove all class files from the user script dir (none should ever be there) - FileFilter classFileFilter = file -> file.getName().endsWith(".class"); - File[] userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); - for (File file : userScriptDirFiles) { + // remove all class files from the user script bin dir + File userScriptsBinDir = + GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile(); + File[] userScriptBinDirFiles; + if (userScriptsBinDir.exists()) { + userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter); + for (File file : userScriptBinDirFiles) { file.delete(); } - userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); - boolean isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0; - assertTrue("Unable to delete class files from the user scripts directory", isEmpty); - - // remove all class files from the user script bin dir - File userScriptsBinDir = GhidraSourceBundle.getBindirFromScriptFile( - new ResourceFile(newScriptFile)).toFile(); - File[] userScriptBinDirFiles; - if (userScriptsBinDir.exists()) { - userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter); - for (File file : userScriptBinDirFiles) { - file.delete(); - } - } - userScriptBinDirFiles = userScriptsDir.listFiles(classFileFilter); - isEmpty = userScriptBinDirFiles == null || userScriptBinDirFiles.length == 0; - assertTrue("Unable to delete class files from the bin directory", isEmpty); - - // compile the script - ScriptTaskListener scriptID = env.runScript(newScriptFile); - waitForScriptCompletion(scriptID, 20000); - - // make sure the class file is in the user dir's bin dir - assertTrue("bin dir was not created!", userScriptsBinDir.exists()); - File classFile = new File(userScriptsBinDir, rawScriptName + ".class"); - assertTrue("Class file was not compiled to the bin dir", classFile.exists()); - - // make sure no other class files are in the user script dir - userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); - isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0; - assertTrue("Class files were written to the top level script directory", isEmpty); - - newScriptFile.delete(); } + userScriptBinDirFiles = userScriptsDir.listFiles(classFileFilter); + isEmpty = userScriptBinDirFiles == null || userScriptBinDirFiles.length == 0; + assertTrue("Unable to delete class files from the bin directory", isEmpty); + + // compile the script + ScriptTaskListener scriptID = env.runScript(newScriptFile); + waitForScriptCompletion(scriptID, 20000); + + // make sure the class file is in the user dir's bin dir + assertTrue("bin dir was not created!", userScriptsBinDir.exists()); + File classFile = new File(userScriptsBinDir, rawScriptName + ".class"); + assertTrue("Class file was not compiled to the bin dir", classFile.exists()); + + // make sure no other class files are in the user script dir + userScriptDirFiles = userScriptsDir.listFiles(classFileFilter); + isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0; + assertTrue("Class files were written to the top level script directory", isEmpty); + + newScriptFile.delete(); } @Test @@ -195,24 +185,22 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // Tests that a system script will not get compiled to the source tree in which it lives, // but will instead get compiled to the user scripts directory // - try (var rewire = unwireGUI()) { - // find a system script - String scriptName = "HelloWorldScript.java"; - ResourceFile systemScriptFile = findScript(scriptName); + // find a system script + String scriptName = "HelloWorldScript.java"; + ResourceFile systemScriptFile = findScript(scriptName); - // compile the system script - ScriptTaskListener scriptID = env.runScript(systemScriptFile.getFile(false)); - waitForScriptCompletion(scriptID, 20000); + // compile the system script + ScriptTaskListener scriptID = env.runScript(systemScriptFile.getFile(false)); + waitForScriptCompletion(scriptID, 20000); - // verify that the generated class file is placed in the default scripting home/bin - File userScriptsBinDir = - GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile(); - String className = scriptName.replace(".java", ".class"); - File expectedClassFile = new File(userScriptsBinDir, className); + // verify that the generated class file is placed in the default scripting home/bin + File userScriptsBinDir = + GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile(); + String className = scriptName.replace(".java", ".class"); + File expectedClassFile = new File(userScriptsBinDir, className); - assertTrue("System script not compiled to the expected directory", - expectedClassFile.exists()); - } + assertTrue("System script not compiled to the expected directory", + expectedClassFile.exists()); } @Test @@ -221,42 +209,39 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // Tests that we can create a user-defined scripts directory and that compiling a // script will put the output in the bin directory under the user settings directory. // - try (var rewire = unwireGUI()) { + // create a user-defined directory + File tempDir = new File(AbstractGTest.getTestDirectoryPath()); + File tempScriptDir = new File(tempDir, "TestScriptDir"); + FileUtilities.deleteDir(tempScriptDir); + tempScriptDir.mkdir(); - // create a user-defined directory - File tempDir = new File(AbstractGTest.getTestDirectoryPath()); - File tempScriptDir = new File(tempDir, "TestScriptDir"); - FileUtilities.deleteDir(tempScriptDir); - tempScriptDir.mkdir(); + ResourceFile scriptDir = new ResourceFile(tempScriptDir); + addScriptDirectory(scriptDir); + try { + // create a script file in that directory + String rawScriptName = testName.getMethodName(); + String scriptFilename = rawScriptName + ".java"; + ResourceFile newScriptFile = new ResourceFile(scriptDir, scriptFilename); - ResourceFile scriptDir = new ResourceFile(tempScriptDir); - addScriptDirectory(scriptDir); - try { - // create a script file in that directory - String rawScriptName = testName.getMethodName(); - String scriptFilename = rawScriptName + ".java"; - ResourceFile newScriptFile = new ResourceFile(scriptDir, scriptFilename); + JavaScriptProvider scriptProvider = new JavaScriptProvider(); + scriptProvider.createNewScript(newScriptFile, null); - JavaScriptProvider scriptProvider = new JavaScriptProvider(); - scriptProvider.createNewScript(newScriptFile, null); + // compile the script + ScriptTaskListener scriptID = env.runScript(newScriptFile.getFile(false)); + waitForScriptCompletion(scriptID, 20000); - // compile the script - ScriptTaskListener scriptID = env.runScript(newScriptFile.getFile(false)); - waitForScriptCompletion(scriptID, 20000); + // verify a bin dir was created and that the class file is in it + File binDir = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile(); + assertTrue("bin output dir not created", binDir.exists()); - // verify a bin dir was created and that the class file is in it - File binDir = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile(); - assertTrue("bin output dir not created", binDir.exists()); + File scriptClassFile = new File(binDir, rawScriptName + ".class"); + assertTrue("Script not compiled to the user-defined script directory", + scriptClassFile.exists()); - File scriptClassFile = new File(binDir, rawScriptName + ".class"); - assertTrue("Script not compiled to the user-defined script directory", - scriptClassFile.exists()); - - deleteFile(newScriptFile); - } - finally { - deleteFile(scriptDir); - } + deleteFile(newScriptFile); + } + finally { + deleteFile(scriptDir); } } @@ -417,7 +402,6 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes @Test public void testSaveAsDoesNotAllowUseOfExistingScriptName() throws Exception { ResourceFile existingScript = createTempScriptFile(); - System.err.printf("secret script: %s\n", existingScript.toString()); refreshProvider();// alert manager to new script loadTempScriptIntoEditor(); @@ -485,16 +469,13 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // Checks for the error where script fields accumulated state because script // instances were reused. Script instances should be recreated for each run. // - try (var rewire = unwireGUI()) { - ResourceFile script = createInstanceFieldScript(); - String output = runScriptAndGetOutput(script); - assertContainsText("*1*", output); + ResourceFile script = createInstanceFieldScript(); + String output = runScriptAndGetOutput(script); + assertContainsText("*1*", output); - output = runScriptAndGetOutput(script); - assertContainsText( - "The field of the script still has state--the script was not recreated", "*1*", - output); - } + output = runScriptAndGetOutput(script); + assertContainsText("The field of the script still has state--the script was not recreated", + "*1*", output); } @Test @@ -503,15 +484,12 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes // If the script is not changed, do not reload, which allows for clients to use // static variables to maintain state. // - try (var rewire = unwireGUI()) { - ResourceFile script = createStaticFieldScript(); - String output = runScriptAndGetOutput(script); - assertContainsText("*1*", output); + ResourceFile script = createStaticFieldScript(); + String output = runScriptAndGetOutput(script); + assertContainsText("*1*", output); - output = runScriptAndGetOutput(script); - assertContainsText( - "The field of the script still has state--the script was not recreated", "*2*", - output); - } + output = runScriptAndGetOutput(script); + assertContainsText("The field of the script still has state--the script was not recreated", + "*2*", output); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java index 83cf5ef4d6..3bbc990083 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java @@ -30,7 +30,6 @@ import docking.action.DockingActionIf; import docking.widgets.filter.FilterTextField; import docking.widgets.list.ListPanel; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.osgi.BundleStatusProvider; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.JavaScriptProvider; import ghidra.util.StringUtilities; @@ -281,15 +280,10 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes performAction(bundleStatusAction, false); waitForSwing(); - final BundleStatusProvider bundleStatusProvider = - waitForComponentProvider(BundleStatusProvider.class); - final ResourceFile dir = new ResourceFile(getTestDirectoryPath() + "/test_scripts"); dir.getFile(false).mkdirs(); - SwingUtilities.invokeLater(() -> { - bundleStatusProvider.getModel().enablePath(dir); - }); + provider.getBundleHost().enablePath(dir); waitForSwing(); pressNewButton(); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index 0822ed0198..111b41f0c8 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -27,7 +27,7 @@ import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import generic.jar.ResourceFile; import ghidra.app.plugin.core.console.ConsoleComponentProvider; -import ghidra.app.plugin.core.osgi.BundleStatusProvider; +import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider; import ghidra.app.plugin.core.script.*; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.services.ConsoleService; @@ -115,11 +115,11 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator paths.add("$GHIDRA_HOME/Features/Base/ghidra_scripts"); paths.add("/User/defined/invalid/directory"); - BundleStatusProvider bundleStatusProvider = showProvider(BundleStatusProvider.class); - bundleStatusProvider.getModel().setPathsForTesting(paths); + BundleStatusComponentProvider bundleStatusComponentProvider = showProvider(BundleStatusComponentProvider.class); + bundleStatusComponentProvider.getModel().setPathsForTesting(paths); - waitForComponentProvider(BundleStatusProvider.class); - captureComponent(bundleStatusProvider.getComponent()); + waitForComponentProvider(BundleStatusComponentProvider.class); + captureComponent(bundleStatusComponentProvider.getComponent()); } @Test