tease out GhidraScriptUtil state, clarify headless and headed cases

- GhidraScriptUtil how has (static) initialize & dispose
  - initialize is either through (headed/GUI) GhidraScriptMgrPlugin or
    directly (headless)
- BundleHost
  - now owns bundle paths (script dirs)
  - save and restore to SaveState for use by GUI
  - no static instances
  - GhidraScriptUtil BundleHost coincides with GUI's when both are used
  - generates events
  - conservative use of auto-vivification
- BundleStatusCompoentProvider / PathManager
  - better split between GUI & model
  - subscribes to GUI's BundleHost for events
  - provider registers for generated events, controls BundleHost
This commit is contained in:
Jason P. Leasure 2020-04-13 17:30:43 -04:00
parent 2c27ca1376
commit 49e3d6dcdc
19 changed files with 849 additions and 743 deletions

View file

@ -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<ResourceFile, GhidraBundle> bp2gb = new HashMap<>();
HashMap<String, GhidraBundle> 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<ResourceFile> paths, boolean enabled, boolean systemBundle) {
Map<ResourceFile, GhidraBundle> 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<GhidraBundle> 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<GhidraBundle> 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<OSGiListener> osgiListeners = new ArrayList<>();
List<BundleHostListener> 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<GhidraBundle> gbundles) {
synchronized (listeners) {
for (BundleHostListener l : listeners) {
l.bundlesAdded(gbundles);
}
}
}
private void fireBundlesRemoved(Collection<GhidraBundle> 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<ResourceFile> bundlePaths = new ArrayList<>();
{
setBundlePaths(GhidraScriptUtil.getSystemScriptPaths());
getBundlePaths().add(0, GhidraScriptUtil.getUserScriptDirectory());
public Collection<GhidraBundle> getGhidraBundles() {
return bp2gb.values();
}
public List<ResourceFile> getBundlePaths() {
return bundlePaths;
public Collection<ResourceFile> getBundlePaths() {
return bp2gb.keySet();
}
public void setBundlePaths(List<ResourceFile> 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<GhidraBundle> 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<ResourceFile, GhidraBundle> 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);
}
}

View file

@ -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<GhidraBundle> gbundles) {
for (GhidraBundle gbundle : gbundles) {
bundleAdded(gbundle);
}
}
default void bundleRemoved(GhidraBundle gbundle) {
//
}
default void bundlesRemoved(Collection<GhidraBundle> gbundles) {
for (GhidraBundle gbundle : gbundles) {
bundleRemoved(gbundle);
}
}
}

View file

@ -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) {
//
}

View file

@ -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<BundleStatus> 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<GhidraBundle> 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<GhidraBundle> 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

View file

@ -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<BundleStatus> {
public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatus> {
List<Column> columns = new ArrayList<>();
class Column {
@ -72,8 +67,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
@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<BundleStatus> {
@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<BundleStatus> {
return badColumn;
}
private BundleStatusProvider provider;
private BundleStatusComponentProvider provider;
private List<BundleStatus> statuses;
private BundleHost bundleHost;
OSGiListener bundleListener;
BundleHostListener bundleListener;
private Map<String, BundleStatus> 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<BundleStatus> {
}
}
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<GhidraBundle> 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<GhidraBundle> gbundles) {
List<BundleStatus> 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<BundleStatus> {
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<BundleStatus> {
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<BundleStatus> {
* @param enabled mark them all as enabled
* @param readonly mark them all as readonly
*/
void addNewPaths(List<File> files, boolean enabled, boolean readonly) {
void addNewStatuses(List<File> 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<BundleStatus> toRemove = Arrays.stream(modelRows).mapToObj(statuses::get).collect(
Collectors.toUnmodifiableList());
removeStatuses(toRemove);
}
void removeStatuses(List<BundleStatus> 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<BundleStatus> {
@Override
public String getName() {
return BundleStatusModel.class.getSimpleName();
return BundleStatusTableModel.class.getSimpleName();
}
@Override
@ -335,31 +369,6 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
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 <code>bundle</code> is managed and not marked readonly
* @param bundle the path to test
@ -385,9 +394,9 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
fireTableDataChanged();
}
private ArrayList<BundleStatusListener> bundleStatusListeners = new ArrayList<>();
private ArrayList<BundleStatusChangeRequestListener> 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<BundleStatus> {
}
}
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<BundleStatus> 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<BundleStatus> 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<BundleStatus> getRowObjects(int[] modelRowIndices) {
List<BundleStatus> rows = new ArrayList<>(modelRowIndices.length);
for (int i : modelRowIndices) {

View file

@ -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;
}

View file

@ -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) {
//
}
}

View file

@ -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) {
//
}
}

View file

@ -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() {

View file

@ -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<ResourceFile> 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<ResourceFile> getScriptDirectories() {
return bundleStatusProvider.getModel().getEnabledPaths().stream().filter(
return bundleStatusComponentProvider.getModel().getEnabledPaths().stream().filter(
ResourceFile::isDirectory).collect(Collectors.toList());
}
public List<ResourceFile> 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<ResourceFile> 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<GhidraBundle> gbundles) {
plugin.getTool().setConfigChanged(true);
performRefresh();
}
@Override
public void bundleRemoved(GhidraBundle gbundle) {
plugin.getTool().setConfigChanged(true);
performRefresh();
}
@Override
public void bundlesRemoved(Collection<GhidraBundle> 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<ResourceFile> 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<ResourceFile> {

View file

@ -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<String, List<ResourceFile>> 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<ResourceFile> newPaths) {
_bundleHost.setBundlePaths(new ArrayList<>(newPaths));
public static void initialize(BundleHost bundleHost, List<ResourceFile> 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<ResourceFile> scriptDirectories,
private static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories,
String filename) {
String validatedName = fixupName(filename);

View file

@ -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);

View file

@ -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()) {

View file

@ -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()) {

View file

@ -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);

View file

@ -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<ResourceFile> scriptBundlePaths =
(List<ResourceFile>) 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:
*
* <pre>{@code
* try (var rewire = unwireGUI()) {
* // ... test code ...
* }
* }</pre>
*
* @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<BundleStatusListener> bundleStatusListeners =
(List<BundleStatusListener>) TestUtils.getInstanceField("bundleStatusListeners",
bundleStatusModel);
List<BundleStatusListener> storedBundleStatusListeners =
new ArrayList<>(bundleStatusListeners);
bundleStatusListeners.clear();
List<OSGiListener> osgiListeners =
(List<OSGiListener>) TestUtils.getInstanceField("osgiListeners",
BundleHost.getInstance());
ArrayList<OSGiListener> 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<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
BundleStatusComponentProvider bundleStatusComponentProvider =
(BundleStatusComponentProvider) TestUtils.getInstanceField("bundleStatusComponentProvider", provider);
List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths();
for (ResourceFile path : paths) {
File file = path.getFile(false);

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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