style fixes for ghidra.app.plugin.core.osgi

This commit is contained in:
Jason P. Leasure
2020-06-05 18:29:31 -04:00
parent 5832d95cdd
commit 6bc33bdf65
23 changed files with 1636 additions and 1164 deletions
File diff suppressed because it is too large Load Diff
@@ -22,39 +22,84 @@ import java.util.Collection;
*/ */
public interface BundleHostListener { public interface BundleHostListener {
default void bundleBuilt(GhidraBundle gbundle, String summary) { /**
* Invoked when a bundle is built.
*
* @param bundle the bundle
* @param summary a summary of the build
*/
default void bundleBuilt(GhidraBundle bundle, String summary) {
// //
} }
default void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablement) { /**
* Invoked when a bundle is enabled or disabled.
*
* @param bundle the bundle
* @param newEnablement true if enabled, false if disabled
*/
default void bundleEnablementChange(GhidraBundle bundle, boolean newEnablement) {
// //
} }
default void bundleActivationChange(GhidraBundle gbundle, boolean newActivation) { /**
* Invoked when a bundle is activated or deactivated.
*
* @param bundle the bundle
* @param newActivation true if activated, false if deactivated
*/
default void bundleActivationChange(GhidraBundle bundle, boolean newActivation) {
// //
} }
default void bundleAdded(GhidraBundle gbundle) { /**
* Invoked when a bundle is added to {@link BundleHost}
*
* @param bundle the bundle
*/
default void bundleAdded(GhidraBundle bundle) {
// //
} }
default void bundlesAdded(Collection<GhidraBundle> gbundles) { /**
for (GhidraBundle gbundle : gbundles) { * Invoked when a number of bundles is added at once. A listener should implement this method
bundleAdded(gbundle); * to avoid repeated invocation of {@link #bundleAdded} in quick succession.
*
* @param bundles the bundles
*/
default void bundlesAdded(Collection<GhidraBundle> bundles) {
for (GhidraBundle bundle : bundles) {
bundleAdded(bundle);
} }
} }
default void bundleRemoved(GhidraBundle gbundle) { /**
* Invoked when a bundle is removed from {@link BundleHost}
*
* @param bundle the bundle
*/
default void bundleRemoved(GhidraBundle bundle) {
// //
} }
default void bundlesRemoved(Collection<GhidraBundle> gbundles) { /**
for (GhidraBundle gbundle : gbundles) { * Invoked when a number of bundles is removed at once. A listener should implement this method
bundleRemoved(gbundle); * to avoid repeated invocation of {@link #bundleRemoved} in quick succession.
*
* @param bundles the bundles
*/
default void bundlesRemoved(Collection<GhidraBundle> bundles) {
for (GhidraBundle bundle : bundles) {
bundleRemoved(bundle);
} }
} }
default void bundleException(GhidraBundleException gbe) { /**
* Invoked when {@link BundleHost} excepts during bundle activation/deactivation.
*
* @param exception the exception thrown
*/
default void bundleException(GhidraBundleException exception) {
// //
} }
@@ -20,75 +20,21 @@ import generic.util.Path;
/** /**
* The BundleStatus class represents the runtime state and user preferences for OSGi bundles in Ghidra. * The BundleStatus class represents the runtime state and user preferences for OSGi bundles in Ghidra.
*
* XXX: this class relies on generic.util.Path solely for the parsing and formatting of USER_HOME and GHIDRA_HOME
*/ */
public class BundleStatus implements Comparable<BundleStatus> { public class BundleStatus implements Comparable<BundleStatus> {
final Path path; final Path path;
final GhidraBundle.Type type; final GhidraBundle.Type type;
final String bundleLoc; final String bundleLocation;
boolean active = false; boolean active = false;
boolean busy = false; boolean busy = false;
public boolean isEnabled() {
return path.isEnabled();
}
public void setEnabled(boolean isEnabled) {
path.setEnabled(isEnabled);
}
public boolean isReadOnly() {
return path.isReadOnly();
}
String summary; String summary;
public GhidraBundle.Type getType() {
return type;
}
BundleStatus(ResourceFile path, boolean enabled, boolean readonly, String bundleLoc) { BundleStatus(ResourceFile path, boolean enabled, boolean readonly, String bundleLoc) {
this.path = new Path(path, enabled, false, readonly); this.path = new Path(path, enabled, false, readonly);
type = GhidraBundle.getType(getPath()); type = GhidraBundle.getType(getPath());
this.bundleLoc = bundleLoc; this.bundleLocation = bundleLoc;
}
public boolean isDirectory() {
return getPath().isDirectory();
}
public boolean isActive() {
return active;
}
public void setActive(boolean b) {
active = b;
}
public void setBusy(boolean b) {
busy = b;
}
public boolean isBusy() {
return busy;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getSummary() {
return summary != null ? summary : "";
}
public ResourceFile getPath() {
return path.getPath();
}
public boolean pathExists() {
return path.exists();
} }
@Override @Override
@@ -96,12 +42,103 @@ public class BundleStatus implements Comparable<BundleStatus> {
return path.compareTo(o != null ? o.path : null); return path.compareTo(o != null ? o.path : null);
} }
/**
* @return true if the bundle is enabled
*/
public boolean isEnabled() {
return path.isEnabled();
}
/**
* Set the bundle's status to enabled or disabled.
*
* @param isEnabled true to set status to enabled
*/
public void setEnabled(boolean isEnabled) {
path.setEnabled(isEnabled);
}
/**
* @return true if the bundle is read only
*/
public boolean isReadOnly() {
return path.isReadOnly();
}
/**
* @return the bundle type
*
* @see GhidraBundle.Type
*/
public GhidraBundle.Type getType() {
return type;
}
/**
* @return true if the bundle is active
*/
public boolean isActive() {
return active;
}
/**
* Set the bundle's status to active or inactive.
*
* @param isActive true for active, false for inactive
*/
public void setActive(boolean isActive) {
active = isActive;
}
/**
* Set the bundle's build summary.
*
* @param summary the build summary
*/
public void setSummary(String summary) {
this.summary = summary;
}
/**
* @return the bundle's build summary
*/
public String getSummary() {
return summary != null ? summary : "";
}
/**
* @return the bundle's path
*/
public ResourceFile getPath() {
return path.getPath();
}
/**
* @return true if the bundle's path exists
*/
public boolean pathExists() {
return path.exists();
}
/**
* @return the bundle's path as a string, using $USER and $GHIDRA_HOME when appropriate
*/
public String getPathAsString() { public String getPathAsString() {
return path.getPathAsString(); return path.getPathAsString();
} }
public String getBundleLoc() { /**
return bundleLoc; * @return the bundle's location identifier
*/
public String getBundleLocation() {
return bundleLocation;
} }
void setBusy(boolean isBusy) {
busy = isBusy;
}
boolean isBusy() {
return busy;
}
} }
@@ -20,11 +20,23 @@ package ghidra.app.plugin.core.osgi;
*/ */
public interface BundleStatusChangeRequestListener { public interface BundleStatusChangeRequestListener {
default public void bundleEnablementChangeRequest(BundleStatus status, boolean newValue) { /**
* Invoked when the user requests that a bundle is enabled/disabled.
*
* @param status the current status
* @param newValue true if enabled, false if disabled
*/
default void bundleEnablementChangeRequest(BundleStatus status, boolean newValue) {
// //
} }
default public void bundleActivationChangeRequest(BundleStatus status, boolean newValue) { /**
* Invoked when the user requests that a bundle is activated/deactivated.
*
* @param status the current status
* @param newValue true if activated, false if deactivated
*/
default void bundleActivationChangeRequest(BundleStatus status, boolean newValue) {
// //
} }
@@ -49,7 +49,10 @@ import resources.ResourceManager;
* component for managing OSGi bundle status * component for managing OSGi bundle status
*/ */
public class BundleStatusComponentProvider extends ComponentProviderAdapter { public class BundleStatusComponentProvider extends ComponentProviderAdapter {
static String preferenceForLastSelectedBundle = "LastGhidraBundle"; static final String BUNDLE_GROUP = "0bundle group";
static final String BUNDLE_LIST_GROUP = "1bundle list group";
static final String PREFENCE_LAST_SELECTED_BUNDLE = "LastGhidraBundle";
private JPanel panel; private JPanel panel;
private LessFreneticGTable bundleStatusTable; private LessFreneticGTable bundleStatusTable;
@@ -60,9 +63,14 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
private GhidraFileFilter filter; private GhidraFileFilter filter;
private final BundleHost bundleHost; private final BundleHost bundleHost;
static final String BUNDLE_GROUP = "0bundle group"; /**
static final String BUNDLE_LIST_GROUP = "1bundle list group"; * {@link BundleStatusComponentProvider} visualizes bundle status and exposes actions for
* adding, removing, enabling, disabling, activating, and deactivating bundles.
*
* @param tool the tool
* @param owner the owner name
* @param bundleHost the bundle host
*/
public BundleStatusComponentProvider(PluginTool tool, String owner, BundleHost bundleHost) { public BundleStatusComponentProvider(PluginTool tool, String owner, BundleHost bundleHost) {
super(tool, "BundleManager", owner); super(tool, "BundleManager", owner);
setHelpLocation(new HelpLocation("BundleManager", "BundleManager")); setHelpLocation(new HelpLocation("BundleManager", "BundleManager"));
@@ -136,12 +144,24 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
// to allow custom cell renderers // to allow custom cell renderers
bundleStatusTable.setAutoCreateColumnsFromModel(false); bundleStatusTable.setAutoCreateColumnsFromModel(false);
configureTableColumns();
filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusTableModel);
JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
scrollPane.getViewport().setBackground(bundleStatusTable.getBackground());
panel.add(filterPanel, BorderLayout.SOUTH);
panel.add(scrollPane, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(800, 400));
}
private void configureTableColumns() {
TableColumn column; TableColumn column;
int skinnyWidth = 60; int skinnyWidth = 60;
// //
column = bundleStatusTable.getColumnModel().getColumn( column = bundleStatusTable.getColumnModel()
bundleStatusTableModel.enabledColumn.index); .getColumn(bundleStatusTableModel.enabledColumn.index);
column.setPreferredWidth(skinnyWidth); column.setPreferredWidth(skinnyWidth);
column.setMinWidth(skinnyWidth); column.setMinWidth(skinnyWidth);
column.setMaxWidth(skinnyWidth); column.setMaxWidth(skinnyWidth);
@@ -171,9 +191,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
setText(""); setText("");
} }
return x; return x;
} }
}); });
// //
@@ -197,80 +215,61 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
return c; return c;
} }
}); });
}
filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusTableModel); private void addBundlesAction(String actionName, String description, Icon icon,
Runnable runnable) {
DockingAction action = new DockingAction(actionName, this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
runnable.run();
}
JScrollPane scrollPane = new JScrollPane(bundleStatusTable); @Override
scrollPane.getViewport().setBackground(bundleStatusTable.getBackground()); public boolean isEnabledForContext(ActionContext context) {
return bundleStatusTable.getSelectedRows().length > 0;
}
};
action.setPopupMenuData(new MenuData(new String[] { description }, icon, BUNDLE_GROUP));
action.setToolBarData(new ToolBarData(icon, BUNDLE_GROUP));
action.setDescription(description);
action.setEnabled(false);
getTool().addLocalAction(this, action);
}
private void addBundleListAction(String actionName, String name, String description, Icon icon,
Runnable runnable) {
DockingAction action = new DockingAction(actionName, this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
runnable.run();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return true;
}
};
action.setPopupMenuData(new MenuData(new String[] { name }, icon, BUNDLE_LIST_GROUP));
action.setToolBarData(new ToolBarData(icon, BUNDLE_LIST_GROUP));
action.setDescription(description);
action.setEnabled(true);
getTool().addLocalAction(this, action);
panel.add(filterPanel, BorderLayout.SOUTH);
panel.add(scrollPane, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(800, 400));
} }
private void createActions() { private void createActions() {
DockingAction action; DockingAction action;
// addBundlesAction("ActivateBundles", "Activate bundle(s)",
action = new DockingAction("ActivateBundles", this.getName()) { ResourceManager.loadImage("images/media-playback-start.png"), this::doActivateBundles);
@Override
public void actionPerformed(ActionContext context) {
doActivateBundles();
}
@Override addBundlesAction("DeactivateBundles", "Deactivate bundle(s)",
public boolean isEnabledForContext(ActionContext context) { ResourceManager.loadImage("images/media-playback-stop.png"), this::doDeactivateBundles);
return bundleStatusTable.getSelectedRows().length > 0;
}
};
action.setPopupMenuData(new MenuData(new String[] { "Activate bundle(s)" },
ResourceManager.loadImage("images/media-playback-start.png"), BUNDLE_GROUP));
action.setToolBarData(new ToolBarData(
ResourceManager.loadImage("images/media-playback-start.png"), BUNDLE_GROUP));
action.setDescription("Activate bundle(s)");
action.setEnabled(false);
getTool().addLocalAction(this, action);
// addBundlesAction("CleanBundles", "Clean bundle(s)",
action = new DockingAction("DeactivateBundles", this.getName()) { ResourceManager.loadImage("images/erase16.png"), this::doClean);
@Override
public void actionPerformed(ActionContext context) {
doDeactivateBundles();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return bundleStatusTable.getSelectedRows().length > 0;
}
};
action.setPopupMenuData(new MenuData(new String[] { "Deactivate bundle(s)" },
ResourceManager.loadImage("images/media-playback-stop.png"), BUNDLE_GROUP));
action.setToolBarData(new ToolBarData(
ResourceManager.loadImage("images/media-playback-stop.png"), BUNDLE_GROUP));
action.setDescription("Deactivate bundle(s)");
action.setEnabled(false);
getTool().addLocalAction(this, action);
//
action = new DockingAction("CleanBundles", this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
doClean();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return bundleStatusTable.getSelectedRows().length > 0;
}
};
// cache is a lightning bolt
action.setPopupMenuData(new MenuData(new String[] { "Clean bundle(s)" },
ResourceManager.loadImage("images/erase16.png"), BUNDLE_GROUP));
action.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/erase16.png"), BUNDLE_GROUP));
action.setDescription("Clean build artifacts for bundle(s)");
action.setEnabled(false);
getTool().addLocalAction(this, action);
// //
action = new DockingAction("AddBundles", this.getName()) { action = new DockingAction("AddBundles", this.getName()) {
@@ -285,16 +284,16 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
} }
}; };
action.setPopupMenuData(new MenuData(new String[] { "Add bundle(s)" }, Icon icon = ResourceManager.loadImage("images/Plus.png");
ResourceManager.loadImage("images/Plus.png"), BUNDLE_LIST_GROUP)); action.setPopupMenuData(
action.setToolBarData( new MenuData(new String[] { "Add bundle(s)" }, icon, BUNDLE_LIST_GROUP));
new ToolBarData(ResourceManager.loadImage("images/Plus.png"), BUNDLE_LIST_GROUP)); action.setToolBarData(new ToolBarData(icon, BUNDLE_LIST_GROUP));
action.setDescription("Display file chooser to add bundles to list"); action.setDescription("Display file chooser to add bundles to list");
action.setEnabled(true); action.setEnabled(true);
getTool().addLocalAction(this, action); getTool().addLocalAction(this, action);
// //
icon = ResourceManager.loadImage("images/edit-delete.png");
action = new DockingAction("RemoveBundles", this.getName()) { action = new DockingAction("RemoveBundles", this.getName()) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@@ -307,11 +306,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
} }
}; };
action.setPopupMenuData(new MenuData(new String[] { "Remove bundle(s)" }, action.setPopupMenuData(
ResourceManager.loadImage("images/edit-delete.png"), BUNDLE_LIST_GROUP)); new MenuData(new String[] { "Remove bundle(s)" }, icon, BUNDLE_LIST_GROUP));
action.setToolBarData(new ToolBarData(ResourceManager.loadImage("images/edit-delete.png"), action.setToolBarData(new ToolBarData(icon, BUNDLE_LIST_GROUP));
BUNDLE_LIST_GROUP));
action.setDescription("Remove selected bundle(s) from the list"); action.setDescription("Remove selected bundle(s) from the list");
action.setEnabled(true); action.setEnabled(true);
getTool().addLocalAction(this, action); getTool().addLocalAction(this, action);
@@ -341,7 +338,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
} }
} }
if (anythingCleaned) { if (anythingCleaned) {
getModel().fireTableDataChanged(); bundleStatusTableModel.fireTableDataChanged();
AnimationUtils.shakeComponent(getComponent()); AnimationUtils.shakeComponent(getComponent());
} }
} }
@@ -354,9 +351,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
doDeactivateBundles(); doDeactivateBundles();
Map<Boolean, List<GhidraBundle>> bundles = Map<Boolean, List<GhidraBundle>> bundles =
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().map( bundleStatusTableModel.getRowObjects(selectedModelRows)
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect( .stream()
Collectors.partitioningBy(gb -> gb.isSystemBundle())); .map(bs -> bundleHost.getExistingGhidraBundle(bs.getPath()))
.collect(Collectors.partitioningBy(gb -> gb.isSystemBundle()));
List<GhidraBundle> systemBundles = bundles.get(true); List<GhidraBundle> systemBundles = bundles.get(true);
if (!systemBundles.isEmpty()) { if (!systemBundles.isEmpty()) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -388,19 +386,19 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
} }
@Override @Override
public boolean accept(File f, GhidraFileChooserModel l_model) { public boolean accept(File f, GhidraFileChooserModel model) {
return filter.accept(f, l_model); return filter.accept(f, model);
} }
}); });
} }
String lastSelected = Preferences.getProperty(preferenceForLastSelectedBundle); String lastSelected = Preferences.getProperty(PREFENCE_LAST_SELECTED_BUNDLE);
if (lastSelected != null) { if (lastSelected != null) {
File f = new File(lastSelected); File f = new File(lastSelected);
fileChooser.setSelectedFile(f); fileChooser.setSelectedFile(f);
} }
} }
else { else {
String lastSelected = Preferences.getProperty(preferenceForLastSelectedBundle); String lastSelected = Preferences.getProperty(PREFENCE_LAST_SELECTED_BUNDLE);
if (lastSelected != null) { if (lastSelected != null) {
File f = new File(lastSelected); File f = new File(lastSelected);
fileChooser.setSelectedFile(f); fileChooser.setSelectedFile(f);
@@ -410,10 +408,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
List<File> files = fileChooser.getSelectedFiles(); List<File> files = fileChooser.getSelectedFiles();
if (!files.isEmpty()) { if (!files.isEmpty()) {
Preferences.setProperty(preferenceForLastSelectedBundle, Preferences.setProperty(PREFENCE_LAST_SELECTED_BUNDLE, files.get(0).getAbsolutePath());
files.get(0).getAbsolutePath());
bundleHost.addGhidraBundles( bundleHost.add(
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()), files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()),
true, false); true, false);
} }
@@ -429,8 +426,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
bundleStatusTable.chill(); bundleStatusTable.chill();
List<BundleStatus> statuses = List<BundleStatus> statuses =
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter( bundleStatusTableModel.getRowObjects(selectedModelRows)
bs -> !bs.isActive()).collect(Collectors.toUnmodifiableList()); .stream()
.filter(bs -> !bs.isActive())
.collect(Collectors.toUnmodifiableList());
List<GhidraBundle> gbs = new ArrayList<>(); List<GhidraBundle> gbs = new ArrayList<>();
for (BundleStatus bs : statuses) { for (BundleStatus bs : statuses) {
@@ -469,16 +468,16 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
new TaskLauncher(new Task("deactivating", true, true, false) { new TaskLauncher(new Task("deactivating", true, true, false) {
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
List<GhidraBundle> gbs = List<GhidraBundle> gbs = bundleStatusTableModel.getRowObjects(selectedModelRows)
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter( .stream()
bs -> bs.isActive()).map( .filter(bs -> bs.isActive())
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect( .map(bs -> bundleHost.getExistingGhidraBundle(bs.getPath()))
Collectors.toList()); .collect(Collectors.toList());
monitor.setMaximum(gbs.size()); monitor.setMaximum(gbs.size());
for (GhidraBundle gb : gbs) { for (GhidraBundle gb : gbs) {
try { try {
bundleHost.deactivateSynchronously(gb.getBundleLoc()); bundleHost.deactivateSynchronously(gb.getBundleLocation());
} }
catch (GhidraBundleException | InterruptedException e) { catch (GhidraBundleException | InterruptedException e) {
e.printStackTrace(console.getStdErr()); e.printStackTrace(console.getStdErr());
@@ -501,10 +500,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
GhidraBundle gb = bundleHost.getExistingGhidraBundle(status.getPath()); GhidraBundle gb = bundleHost.getExistingGhidraBundle(status.getPath());
if (activate) { if (activate) {
gb.build(console.getStdErr()); gb.build(console.getStdErr());
bundleHost.activateSynchronously(gb.getBundleLoc()); bundleHost.activateSynchronously(gb.getBundleLocation());
} }
else { // deactivate else { // deactivate
bundleHost.deactivateSynchronously(gb.getBundleLoc()); bundleHost.deactivateSynchronously(gb.getBundleLocation());
} }
} }
catch (Exception e) { catch (Exception e) {
@@ -518,18 +517,14 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}, null, 1000); }, null, 1000);
} }
public BundleStatusTableModel getModel() { private void notifyTableRowChanged(BundleStatus status) {
return bundleStatusTableModel;
}
public void notifyTableRowChanged(BundleStatus status) {
int modelRowIndex = bundleStatusTableModel.getRowIndex(status); int modelRowIndex = bundleStatusTableModel.getRowIndex(status);
int viewRowIndex = filterPanel.getViewRow(modelRowIndex); int viewRowIndex = filterPanel.getViewRow(modelRowIndex);
bundleStatusTable.notifyTableChanged( bundleStatusTable
new TableModelEvent(bundleStatusTableModel, viewRowIndex)); .notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex));
} }
public void notifyTableDataChanged() { private void notifyTableDataChanged() {
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel)); bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel));
} }
@@ -545,6 +540,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
} }
*/ */
/**
* cleanup this component
*/
public void dispose() { public void dispose() {
bundleStatusTable.dispose(); bundleStatusTable.dispose();
} }
@@ -553,4 +551,17 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
bundleStatusTable.selectRow(filterPanel.getViewRow(modelRowIndex)); bundleStatusTable.selectRow(filterPanel.getViewRow(modelRowIndex));
} }
/**
* This is for testing only! during normal execution, statuses are only added through BundleHostListener bundle(s) added events.
*
* each path is marked editable and non-readonly
*
* @param bundlePaths the paths to use
*/
public void setPathsForTesting(List<ResourceFile> bundlePaths) {
bundleStatusTableModel.setModelData(bundlePaths.stream()
.map(f -> new BundleStatus(f, true, false, null))
.collect(Collectors.toList()));
}
} }
@@ -71,7 +71,7 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
Column activeColumn = new Column("Active", Boolean.class) { Column activeColumn = new Column("Active", Boolean.class) {
@Override @Override
boolean editable(BundleStatus status) { boolean editable(BundleStatus status) {
return status.pathExists(); // XXX maybe only if it's already enabled return status.pathExists() && status.isEnabled();
} }
@Override @Override
@@ -110,31 +110,80 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
} }
Column getColumn(int i) {
if (i >= 0 && i < columns.size()) {
return columns.get(i);
}
return badColumn;
}
private BundleStatusComponentProvider provider;
private List<BundleStatus> statuses;
private BundleHost bundleHost; private BundleHost bundleHost;
BundleHostListener bundleListener; private BundleStatusComponentProvider provider;
private Map<String, BundleStatus> bundleLocToStatusMap = new HashMap<>();
private BundleHostListener bundleHostListener;
private Map<String, BundleStatus> loc2status = new HashMap<>(); private ArrayList<BundleStatusChangeRequestListener> bundleStatusListeners = new ArrayList<>();
private List<BundleStatus> statuses;
BundleStatus getStatus(GhidraBundle gb) { protected class MyBundleHostListener implements BundleHostListener {
return getStatusFromLoc(gb.getBundleLoc()); @Override
} public void bundleBuilt(GhidraBundle bundle, String summary) {
BundleStatus status = getStatus(bundle);
BundleStatus getStatusFromLoc(String bundleLoc) { status.setSummary(summary);
BundleStatus status = loc2status.get(bundleLoc); int row = getRowIndex(status);
if (status == null) { fireTableRowsUpdated(row, row);
Msg.showError(BundleStatusTableModel.this, provider.getComponent(), }
"bundle status error", "bundle has no status!");
@Override
public void bundleActivationChange(GhidraBundle bundle, boolean newActivation) {
BundleStatus status = getStatus(bundle);
int row = getRowIndex(status);
status.setBusy(false);
if (newActivation) {
status.setActive(true);
}
else {
status.setActive(false);
}
fireTableRowsUpdated(row, row);
}
@Override
public void bundleAdded(GhidraBundle bundle) {
addNewStatus(bundle);
}
@Override
public void bundlesAdded(Collection<GhidraBundle> bundles) {
int index = statuses.size();
for (GhidraBundle bundle : bundles) {
addNewStatusNoFire(bundle);
}
fireTableRowsInserted(index, bundles.size() - 1);
}
@Override
public void bundleRemoved(GhidraBundle bundle) {
BundleStatus status = getStatus(bundle);
removeStatus(status);
}
@Override
public void bundlesRemoved(Collection<GhidraBundle> bundles) {
List<BundleStatus> toRemove = bundles.stream()
.map(BundleStatusTableModel.this::getStatus)
.collect(Collectors.toUnmodifiableList());
removeStatuses(toRemove);
}
@Override
public void bundleEnablementChange(GhidraBundle bundle, boolean newEnablement) {
BundleStatus status = getStatus(bundle);
status.setEnabled(newEnablement);
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
@Override
public void bundleException(GhidraBundleException exception) {
BundleStatus status = getStatusFromLoc(exception.getBundleLocation());
status.setSummary(exception.getMessage());
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
} }
return status;
} }
BundleStatusTableModel(BundleStatusComponentProvider provider, BundleHost bundleHost) { BundleStatusTableModel(BundleStatusComponentProvider provider, BundleHost bundleHost) {
@@ -142,114 +191,57 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
this.provider = provider; this.provider = provider;
this.bundleHost = bundleHost; this.bundleHost = bundleHost;
statuses = new ArrayList<>(); statuses = new ArrayList<>();
for (GhidraBundle gb : bundleHost.getGhidraBundles()) { for (GhidraBundle bundle : bundleHost.getGhidraBundles()) {
addNewStatus(gb); addNewStatus(bundle);
} }
bundleHost.addListener(bundleListener = new BundleHostListener() { bundleHost.addListener(bundleHostListener = new MyBundleHostListener());
@Override }
public void bundleBuilt(GhidraBundle gb, String summary) {
BundleStatus status = getStatus(gb);
status.setSummary(summary);
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
@Override Column getColumn(int i) {
public void bundleActivationChange(GhidraBundle gb, boolean newActivation) { if (i >= 0 && i < columns.size()) {
BundleStatus status = getStatus(gb); return columns.get(i);
int row = getRowIndex(status); }
status.setBusy(false); return badColumn;
if (newActivation) { }
status.setActive(true);
}
else {
status.setActive(false);
}
fireTableRowsUpdated(row, row);
}
@Override BundleStatus getStatus(GhidraBundle bundle) {
public void bundleAdded(GhidraBundle gb) { return getStatusFromLoc(bundle.getBundleLocation());
addNewStatus(gb); }
}
@Override BundleStatus getStatusFromLoc(String bundleLoc) {
public void bundlesAdded(Collection<GhidraBundle> gbundles) { BundleStatus status = bundleLocToStatusMap.get(bundleLoc);
int index = statuses.size(); if (status == null) {
for (GhidraBundle gb : gbundles) { Msg.showError(BundleStatusTableModel.this, provider.getComponent(),
addNewStatusNoFire(gb); "bundle status error", "bundle has no status!");
} }
fireTableRowsInserted(index, gbundles.size() - 1); return status;
}
@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
public void bundleException(GhidraBundleException gbe) {
BundleStatus status = getStatusFromLoc(gbe.getBundleLocation());
status.setSummary(gbe.getMessage());
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
});
} }
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
bundleHost.removeListener(bundleListener); bundleHost.removeListener(bundleHostListener);
} }
public List<ResourceFile> getEnabledPaths() { private void addNewStatusNoFire(GhidraBundle bundle) {
List<ResourceFile> list = new ArrayList<>(); BundleStatus status = new BundleStatus(bundle.getPath(), bundle.isEnabled(),
for (BundleStatus status : statuses) { bundle.isSystemBundle(), bundle.getBundleLocation());
if (status.isEnabled()) {
list.add(status.getPath());
}
}
return list;
}
private void addNewStatusNoFire(GhidraBundle gb) {
BundleStatus status =
new BundleStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle(), gb.getBundleLoc());
if (statuses.contains(status)) { if (statuses.contains(status)) {
throw new RuntimeException( throw new RuntimeException(
"Bundle status manager already contains " + gb.getPath().toString()); "Bundle status manager already contains " + bundle.getPath().toString());
} }
status.setActive(gb.isActive()); status.setActive(bundle.isActive());
loc2status.put(status.getBundleLoc(), status); bundleLocToStatusMap.put(status.getBundleLocation(), status);
statuses.add(status); statuses.add(status);
} }
/** /**
* add new status and fire a table update * add new status and fire a table update
*/ */
private void addNewStatus(GhidraBundle gb) { private void addNewStatus(GhidraBundle bundle) {
int index = statuses.size(); int index = statuses.size();
addNewStatusNoFire(gb); addNewStatusNoFire(bundle);
fireTableRowsInserted(index, index); fireTableRowsInserted(index, index);
} }
@@ -257,7 +249,7 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
if (!status.isReadOnly()) { if (!status.isReadOnly()) {
int i = statuses.indexOf(status); int i = statuses.indexOf(status);
statuses.remove(i); statuses.remove(i);
loc2status.remove(status.getBundleLoc()); bundleLocToStatusMap.remove(status.getBundleLocation());
return i; return i;
} }
return -1; return -1;
@@ -271,8 +263,9 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
} }
void remove(int[] modelRows) { void remove(int[] modelRows) {
List<BundleStatus> toRemove = Arrays.stream(modelRows).mapToObj(statuses::get).collect( List<BundleStatus> toRemove = Arrays.stream(modelRows)
Collectors.toUnmodifiableList()); .mapToObj(statuses::get)
.collect(Collectors.toUnmodifiableList());
removeStatuses(toRemove); removeStatuses(toRemove);
} }
@@ -315,7 +308,7 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
public void setValueAt(Object aValue, int rowIndex, int columnIndex) { public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
BundleStatus status = statuses.get(rowIndex); BundleStatus status = statuses.get(rowIndex);
getColumn(columnIndex).setValue(status, aValue); getColumn(columnIndex).setValue(status, aValue);
// XXX I don't know why it's unselected, but it's maddening // anything that's clicked on should become selected!
provider.selectModelRow(rowIndex); provider.selectModelRow(rowIndex);
} }
@@ -339,8 +332,19 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
return statuses; return statuses;
} }
private ArrayList<BundleStatusChangeRequestListener> bundleStatusListeners = new ArrayList<>(); void setModelData(List<BundleStatus> statuses) {
this.statuses = statuses;
computeCache();
fireTableDataChanged();
}
/**
* Add a change request listener.
*
* When the user requests a change to the status of a bundle, each listener is called.
*
* @param listener the listener to add
*/
public void addListener(BundleStatusChangeRequestListener listener) { public void addListener(BundleStatusChangeRequestListener listener) {
synchronized (bundleStatusListeners) { synchronized (bundleStatusListeners) {
if (!bundleStatusListeners.contains(listener)) { if (!bundleStatusListeners.contains(listener)) {
@@ -349,6 +353,11 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
} }
} }
/**
* Remove change request listener.
*
* @param listener the listener to remove
*/
public void removeListener(BundleStatusChangeRequestListener listener) { public void removeListener(BundleStatusChangeRequestListener listener) {
synchronized (bundleStatusListeners) { synchronized (bundleStatusListeners) {
bundleStatusListeners.remove(listener); bundleStatusListeners.remove(listener);
@@ -371,6 +380,12 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
} }
} }
/**
* return the row objects corresponding an array of model row indices.
*
* @param modelRowIndices row indices
* @return status objects
*/
public List<BundleStatus> getRowObjects(int[] modelRowIndices) { public List<BundleStatus> getRowObjects(int[] modelRowIndices) {
List<BundleStatus> rows = new ArrayList<>(modelRowIndices.length); List<BundleStatus> rows = new ArrayList<>(modelRowIndices.length);
for (int i : modelRowIndices) { for (int i : modelRowIndices) {
@@ -418,24 +433,10 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
* (re)compute cached mapping from bundleloc to bundlepath * (re)compute cached mapping from bundleloc to bundlepath
*/ */
private void computeCache() { private void computeCache() {
loc2status.clear(); bundleLocToStatusMap.clear();
for (BundleStatus status : statuses) { for (BundleStatus status : statuses) {
loc2status.put(status.getBundleLoc(), status); bundleLocToStatusMap.put(status.getBundleLocation(), status);
} }
} }
/**
* This is for testing only! during normal execution, statuses are only added through BundleHostListener bundle(s) added events.
*
* each path is marked editable and non-readonly
*
* @param paths the statuses to use
*/
public void setPathsForTesting(List<ResourceFile> paths) {
this.statuses = paths.stream().map(f -> new BundleStatus(f, true, false, null)).collect(
Collectors.toList());
computeCache();
fireTableDataChanged();
}
} }
@@ -24,6 +24,9 @@ import org.osgi.framework.wiring.BundleRequirement;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
/**
* Proxy for an OSGi bundle that may require being built.
*/
public abstract class GhidraBundle { public abstract class GhidraBundle {
protected final ResourceFile path; protected final ResourceFile path;
@@ -31,9 +34,10 @@ public abstract class GhidraBundle {
protected boolean enabled; protected boolean enabled;
protected boolean systemBundle; protected boolean systemBundle;
GhidraBundle(BundleHost bundleHost, ResourceFile path, boolean enabled, boolean systemBundle) { GhidraBundle(BundleHost bundleHost, ResourceFile bundlePath, boolean enabled,
boolean systemBundle) {
this.bundleHost = bundleHost; this.bundleHost = bundleHost;
this.path = path; this.path = bundlePath;
this.enabled = enabled; this.enabled = enabled;
this.systemBundle = systemBundle; this.systemBundle = systemBundle;
} }
@@ -48,45 +52,97 @@ public abstract class GhidraBundle {
/** /**
* build OSGi bundle if possible * build OSGi bundle if possible
* *
* @param writer console for user messages * @param writer console for build messages to user
* @return true if build happened, false if already built * @return true if build happened, false if already built
* @throws Exception sorry, wasn't possible * @throws Exception if the build cannot complete
*/ */
public abstract boolean build(PrintWriter writer) throws Exception; public abstract boolean build(PrintWriter writer) throws Exception;
/**
* same as {@link #build(PrintWriter)} with writer = {@link System.err}.
*/
@SuppressWarnings("javadoc")
public boolean build() throws Exception { public boolean build() throws Exception {
return build(new PrintWriter(System.err)); return build(new PrintWriter(System.err));
} }
public abstract String getBundleLoc(); /**
* Return the location identifier of the bundle that this GhidraBundle represents.
* The location identifier is passed to {@link org.osgi.framework.BundleContext#installBundle} when this
* bundle is installed.
*
* @return location identifier of this bundle
*/
public abstract String getBundleLocation();
abstract List<BundleRequirement> getAllReqs(); abstract List<BundleRequirement> getAllRequirements();
/**
* @return this bundle's path
*/
public ResourceFile getPath() { public ResourceFile getPath() {
return path; return path;
} }
/**
* @return true if this bundle is enabled
*/
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
public void setEnabled(boolean enabled) { /**
* set the enablement flag for this bundle.
*
* If a bundle is enabled its contents will be scanned, e.g. for scripts.
*
* @param enabled new state
*/
void setEnabled(boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }
/**
* If a bundle is a "system bundle" it cannot be removed and its contends cannot be edited.
*
* @return true if this is a system bundle
*/
public boolean isSystemBundle() { public boolean isSystemBundle() {
return systemBundle; return systemBundle;
} }
/**
* A GhidraBundle can be
* <ul>
* <li>a Bndtools .bnd script</li>
* <li>an OSGi bundle .jar file</li>
* <li>a directory of Java source</li>
* </u>
*
*/
enum Type { enum Type {
BndScript, Jar, SourceDir, INVALID BndScript, Jar, SourceDir, INVALID
} }
static GhidraBundle.Type getType(ResourceFile rf) { /**
if (rf.isDirectory()) { * a string error with a time stamp
*/
public static class BuildFailure {
long when = -1;
StringBuilder message = new StringBuilder();
}
/**
* Get the type of a GhidraBundle from its path.
*
* @param path a resource path
* @return the type
*/
static GhidraBundle.Type getType(ResourceFile path) {
if (path.isDirectory()) {
return GhidraBundle.Type.SourceDir; return GhidraBundle.Type.SourceDir;
} }
String n = rf.getName().toLowerCase(); String n = path.getName().toLowerCase();
if (n.endsWith(".bnd")) { if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript; return GhidraBundle.Type.BndScript;
} }
@@ -96,11 +152,17 @@ public abstract class GhidraBundle {
return GhidraBundle.Type.INVALID; return GhidraBundle.Type.INVALID;
} }
static public GhidraBundle.Type getType(File f) { /**
if (f.isDirectory()) { * Get the type of a GhidraBundle from its path.
*
* @param path a file system path
* @return the type
*/
public static GhidraBundle.Type getType(File path) {
if (path.isDirectory()) {
return GhidraBundle.Type.SourceDir; return GhidraBundle.Type.SourceDir;
} }
String n = f.getName().toLowerCase(); String n = path.getName().toLowerCase();
if (n.endsWith(".bnd")) { if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript; return GhidraBundle.Type.BndScript;
} }
@@ -110,21 +172,20 @@ public abstract class GhidraBundle {
return GhidraBundle.Type.INVALID; return GhidraBundle.Type.INVALID;
} }
public Bundle getBundle() { /**
return bundleHost.getBundle(getBundleLoc()); * Get the OSGi bundle respresented by this GhidraBundle or null
} *
* @return a Bundle or null
public void activate() throws Exception { */
activate(new PrintWriter(System.err)); public Bundle getOSGiBundle() {
} return bundleHost.getOSGiBundle(getBundleLocation());
public void activate(PrintWriter writer) throws Exception {
build(writer);
bundleHost.activateSynchronously(getBundleLoc());
} }
/**
* @return true if this bundle is active
*/
public boolean isActive() { public boolean isActive() {
Bundle b = getBundle(); Bundle b = getOSGiBundle();
return (b != null) && b.getState() == Bundle.ACTIVE; return (b != null) && b.getState() == Bundle.ACTIVE;
} }
@@ -19,18 +19,18 @@ import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
public abstract class GhidraBundleActivator implements BundleActivator { public abstract class GhidraBundleActivator implements BundleActivator {
protected abstract void start(BundleContext bc, Object api); protected abstract void start(BundleContext bundleContext, Object api);
protected abstract void stop(BundleContext bc, Object api); protected abstract void stop(BundleContext bundleContext, Object api);
@Override @Override
final public void start(BundleContext bc) throws Exception { public final void start(BundleContext bundleContext) throws Exception {
start(bc, null); start(bundleContext, null);
} }
@Override @Override
final public void stop(BundleContext bc) throws Exception { public final void stop(BundleContext bundleContext) throws Exception {
stop(bc, null); stop(bundleContext, null);
} }
} }
@@ -21,28 +21,52 @@ import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException; import org.osgi.framework.BundleException;
public class GhidraBundleException extends OSGiException { public class GhidraBundleException extends OSGiException {
private Bundle bundle; private final Bundle bundle;
private String bundle_loc; private final String bundleLocation;
/**
* {@link GhidraBundleException}s store the context associated with exceptions thrown during bundle operations.
*
* @param bundle the bundle (if available)
* @param msg a contextual message
* @param cause the original exception
*/
public GhidraBundleException(Bundle bundle, String msg, BundleException cause) { public GhidraBundleException(Bundle bundle, String msg, BundleException cause) {
super(msg + ": " + parsedCause(cause), cause); super(msg + ": " + parsedCause(cause), cause);
this.bundle = bundle; this.bundle = bundle;
this.bundleLocation = bundle.getLocation();
} }
public GhidraBundleException(String bundle_loc, String msg, BundleException cause) { /**
* {@link GhidraBundleException}s store the context associated with exceptions thrown during bundle operations.
*
* @param bundleLocation the bundle location identifier (since no bundle is available)
* @param msg a contextual message
* @param cause the original exception
*/
public GhidraBundleException(String bundleLocation, String msg, BundleException cause) {
super(msg + ": " + parsedCause(cause), cause); super(msg + ": " + parsedCause(cause), cause);
this.bundle_loc = bundle_loc; this.bundle = null;
this.bundleLocation = bundleLocation;
} }
/**
* @return the associated bundle, or null. If null, the bundle location identifier will be non-null
*/
public Bundle getBundle() { public Bundle getBundle() {
return bundle; return bundle;
} }
/**
* When no {@link Bundle} is available, {@link #getBundle()} will return {@code null}.
*
* @return the bundle location identifier of the offending bundle.
*/
public String getBundleLocation() { public String getBundleLocation() {
return bundle_loc != null ? bundle_loc : bundle.getLocation(); return bundleLocation != null ? bundleLocation : bundle.getLocation();
} }
static private String parsedCause(Throwable e) { private static String parsedCause(Throwable e) {
if (e == null) { if (e == null) {
return ""; return "";
} }
@@ -81,9 +105,10 @@ public class GhidraBundleException extends OSGiException {
return message; return message;
} }
// parse the package constraints from filters in the BundleRequirement string // parse the package constraints from filters in the BundleRequirement string
String packages = String packages = OSGiUtils.extractPackageNamesFromFailedResolution(be.getMessage())
OSGiUtils.extractPackages(be.getMessage()).stream().distinct().collect( .stream()
Collectors.joining("\n")); .distinct()
.collect(Collectors.joining("\n"));
return "RESOLVE_ERROR with reference to packages:\n" + packages; return "RESOLVE_ERROR with reference to packages:\n" + packages;
} }
@@ -26,13 +26,24 @@ import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Jar;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
/**
* Proxy to an ordinary OSGi Jar bundle. {@link GhidraJarBundle#build(PrintWriter)} does nothing.
*/
public class GhidraJarBundle extends GhidraBundle { public class GhidraJarBundle extends GhidraBundle {
final String bundleLoc; final String bundleLocation;
/**
* {@link GhidraJarBundle} wraps an ordinary OSGi bundle .jar.
*
* @param bundleHost the {@link BundleHost} instance this bundle will belong to
* @param path the jar file's path
* @param enabled true to start enabled
* @param systemBundle true if this is a Ghidra system bundle
*/
public GhidraJarBundle(BundleHost bundleHost, ResourceFile path, boolean enabled, public GhidraJarBundle(BundleHost bundleHost, ResourceFile path, boolean enabled,
boolean systemBundle) { boolean systemBundle) {
super(bundleHost, path, enabled, systemBundle); super(bundleHost, path, enabled, systemBundle);
this.bundleLoc = "file://" + path.getAbsolutePath().toString(); this.bundleLocation = "file://" + path.getAbsolutePath().toString();
} }
@Override @Override
@@ -46,19 +57,19 @@ public class GhidraJarBundle extends GhidraBundle {
} }
@Override @Override
public String getBundleLoc() { public String getBundleLocation() {
return bundleLoc; return bundleLocation;
} }
@Override @Override
public List<BundleRequirement> getAllReqs() { public List<BundleRequirement> getAllRequirements() {
Jar jar; Jar jar;
try { try {
jar = new Jar(path.getFile(true)); jar = new Jar(path.getFile(true));
Manifest m = jar.getManifest(); Manifest m = jar.getManifest();
String imps = m.getMainAttributes().getValue(Constants.IMPORT_PACKAGE); String imps = m.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
if (imps != null) { if (imps != null) {
return BundleHost.parseImports(imps); return OSGiUtils.parseImports(imps);
} }
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -23,11 +23,14 @@ import org.osgi.framework.wiring.BundleRequirement;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
/**
* {@link GhidraPlaceholderBundle} represents invalid bundle paths in the GUI.
*/
public class GhidraPlaceholderBundle extends GhidraBundle { public class GhidraPlaceholderBundle extends GhidraBundle {
GhidraPlaceholderBundle(BundleHost bundleHost, ResourceFile path, boolean enabled, GhidraPlaceholderBundle(BundleHost bundleHost, ResourceFile bundlePath, boolean isEnabled,
boolean systemBundle) { boolean isSystemBundle) {
super(bundleHost, path, enabled, systemBundle); super(bundleHost, bundlePath, isEnabled, isSystemBundle);
} }
@Override @Override
@@ -41,12 +44,12 @@ public class GhidraPlaceholderBundle extends GhidraBundle {
} }
@Override @Override
public String getBundleLoc() { public String getBundleLocation() {
return "invalid://" + getPath(); return "invalid://" + getPath();
} }
@Override @Override
List<BundleRequirement> getAllReqs() { List<BundleRequirement> getAllRequirements() {
return Collections.emptyList(); return Collections.emptyList();
} }
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,7 @@ import docking.widgets.table.*;
/** /**
* RowObjectSelectionManager attempts to repair selections in a filtered table * RowObjectSelectionManager attempts to repair selections in a filtered table
* before and after filter events. The additiona selection events, however, cause focus changes we don't want. * before and after filter events. The additional selection events, however, cause focus changes we don't want.
*/ */
class LessFreneticGTable extends GTable { class LessFreneticGTable extends GTable {
boolean chilled = false; boolean chilled = false;
@@ -35,27 +35,29 @@ class LessFreneticGTable extends GTable {
} }
@Override @Override
public void tableChanged(TableModelEvent e) { public void tableChanged(TableModelEvent event) {
if (!chilled) { if (!chilled) {
super.tableChanged(e); super.tableChanged(event);
} }
} }
} }
LessFreneticGTable(TableModel model) {
super(model);
}
/** suppress issuing table change events */
public void chill() { public void chill() {
chilled = true; chilled = true;
} }
/** resume issuing table change events */
public void thaw() { public void thaw() {
chilled = false; chilled = false;
notifyTableChanged(new TableModelEvent(getModel())); notifyTableChanged(new TableModelEvent(getModel()));
} }
LessFreneticGTable(TableModel dm) {
super(dm);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected <T> SelectionManager createSelectionManager(TableModel model) { protected <T> SelectionManager createSelectionManager(TableModel model) {
@@ -14,15 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.osgi; package ghidra.app.plugin.core.osgi;
import ghidra.util.exception.UsrException; import ghidra.util.exception.UsrException;
public class OSGiException extends UsrException { public class OSGiException extends UsrException {
public OSGiException(String msg, Throwable cause) { /**
super(msg, cause); * Wrapper for exceptions originating with an OSGi operation.
*
* @param message a contextual message
* @param cause the original exception
*/
public OSGiException(String message, Throwable cause) {
super(message, cause);
} }
public OSGiException(String msg) { /**
super(msg); * Wrapper for exceptions originating with an OSGi operation.
*
* @param message a contextual message
*/
public OSGiException(String message) {
super(message);
} }
} }
@@ -15,19 +15,151 @@
*/ */
package ghidra.app.plugin.core.osgi; package ghidra.app.plugin.core.osgi;
import java.util.List; import java.io.File;
import java.util.Scanner; import java.io.IOException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
import org.osgi.framework.*;
import org.osgi.framework.wiring.BundleRequirement;
public class OSGiUtils { public class OSGiUtils {
public static List<String> extractPackages(String str) { /**
try (Scanner s = new Scanner(str)) { * The syntax of the error generated when OSGi requirements cannot be resolved is
* difficult to parse, so we try to extract package names.
*
* @param osgiExceptionMessage the exception message
* @return a list of package names
*/
static List<String> extractPackageNamesFromFailedResolution(String osgiExceptionMessage) {
try (Scanner s = new Scanner(osgiExceptionMessage)) {
return s.findAll(Pattern.compile("\\(osgi\\.wiring\\.package=([^)]*)\\)")).map(m -> { return s.findAll(Pattern.compile("\\(osgi\\.wiring\\.package=([^)]*)\\)")).map(m -> {
return m.group(1); return m.group(1);
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
} }
static String getEventTypeString(BundleEvent e) {
switch (e.getType()) {
case BundleEvent.INSTALLED:
return "INSTALLED";
case BundleEvent.RESOLVED:
return "RESOLVED";
case BundleEvent.LAZY_ACTIVATION:
return "LAZY_ACTIVATION";
case BundleEvent.STARTING:
return "STARTING";
case BundleEvent.STARTED:
return "STARTED";
case BundleEvent.STOPPING:
return "STOPPING";
case BundleEvent.STOPPED:
return "STOPPED";
case BundleEvent.UPDATED:
return "UPDATED";
case BundleEvent.UNRESOLVED:
return "UNRESOLVED";
case BundleEvent.UNINSTALLED:
return "UNINSTALLED";
default:
return "???";
}
}
/**
* parse Import-Package string from a bundle manifest
*
* @param imports Import-Package value
* @return deduced requirements or null if there was an error
* @throws BundleException on parse failure
*/
static List<BundleRequirement> parseImports(String imports) throws BundleException {
// parse it with Felix's ManifestParser to a list of BundleRequirement objects
Map<String, Object> headerMap = new HashMap<>();
headerMap.put(Constants.IMPORT_PACKAGE, imports);
ManifestParser mp;
mp = new ManifestParser(null, null, null, headerMap);
return mp.getRequirements();
}
// from https://dzone.com/articles/locate-jar-classpath-given
static String findJarForClass(Class<?> c) {
final URL location;
final String classLocation = c.getName().replace('.', '/') + ".class";
final ClassLoader loader = c.getClassLoader();
if (loader == null) {
location = ClassLoader.getSystemResource(classLocation);
}
else {
location = loader.getResource(classLocation);
}
if (location != null) {
Pattern p = Pattern.compile("^.*:(.*)!.*$");
Matcher m = p.matcher(location.toString());
if (m.find()) {
return m.group(1);
}
return null; // not loaded from jar?
}
return null;
}
static void getPackagesFromClasspath(Set<String> s) {
getClasspathElements().forEach(p -> {
if (Files.isDirectory(p)) {
collectPackagesFromDirectory(p, s);
}
else if (p.toString().endsWith(".jar")) {
collectPackagesFromJar(p, s);
}
});
}
static Stream<Path> getClasspathElements() {
String classpathStr = System.getProperty("java.class.path");
return Collections.list(new StringTokenizer(classpathStr, File.pathSeparator))
.stream()
.map(String.class::cast)
.map(Paths::get)
.map(Path::normalize);
}
static void collectPackagesFromDirectory(Path dirPath, Set<String> s) {
try {
Files.walk(dirPath).filter(p -> p.toString().endsWith(".class")).forEach(p -> {
String n = dirPath.relativize(p).toString();
int lastSlash = n.lastIndexOf(File.separatorChar);
s.add(lastSlash > 0 ? n.substring(0, lastSlash).replace(File.separatorChar, '.')
: "");
});
}
catch (IOException e) {
e.printStackTrace();
}
}
static void collectPackagesFromJar(Path jarPath, Set<String> s) {
try {
try (JarFile j = new JarFile(jarPath.toFile())) {
j.stream().filter(je -> je.getName().endsWith(".class")).forEach(je -> {
String n = je.getName();
int lastSlash = n.lastIndexOf('/');
s.add(lastSlash > 0 ? n.substring(0, lastSlash).replace('/', '.') : "");
});
}
}
catch (IOException e) {
e.printStackTrace();
}
}
} }
@@ -578,10 +578,10 @@ class GhidraScriptActionManager {
} }
private void launchJavadoc() { private void launchJavadoc() {
URI URI = entryFile.toURI(); URI uri = entryFile.toURI();
URL URL = null; URL url = null;
try { try {
URL = URI.toURL(); url = uri.toURL();
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
// shouldn't happen // shouldn't happen
@@ -591,7 +591,7 @@ class GhidraScriptActionManager {
return; return;
} }
BrowserLoader.display(URL, URL, plugin.getTool()); BrowserLoader.display(url, url, plugin.getTool());
} }
private void writeZipEntry(File unzipDirectory, ZipEntry entry, InputStream inputStream) private void writeZipEntry(File unzipDirectory, ZipEntry entry, InputStream inputStream)
@@ -41,7 +41,6 @@ import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.BreadthFirstIterator; import docking.widgets.tree.support.BreadthFirstIterator;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.*; import ghidra.app.plugin.core.osgi.*;
import ghidra.app.plugin.core.osgi.BundleHost.BuildFailure;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.services.ConsoleService; import ghidra.app.services.ConsoleService;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
@@ -66,12 +65,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
static final String WINDOW_GROUP = "Script Group"; static final String WINDOW_GROUP = "Script Group";
private Map<ResourceFile, GhidraScriptEditorComponentProvider> editorMap = new HashMap<>(); private Map<ResourceFile, GhidraScriptEditorComponentProvider> editorMap = new HashMap<>();
final private GhidraScriptMgrPlugin plugin; private final GhidraScriptMgrPlugin plugin;
private JPanel component; private JPanel component;
private RootNode scriptRoot; private RootNode scriptRoot;
private GTree scriptCategoryTree; private GTree scriptCategoryTree;
private DraggableScriptTable scriptTable; private DraggableScriptTable scriptTable;
final private GhidraScriptInfoManager infoManager; private final GhidraScriptInfoManager infoManager;
private GhidraScriptTableModel tableModel; private GhidraScriptTableModel tableModel;
private BundleStatusComponentProvider bundleStatusComponentProvider; private BundleStatusComponentProvider bundleStatusComponentProvider;
private TaskListener taskListener = new ScriptTaskListener(); private TaskListener taskListener = new ScriptTaskListener();
@@ -99,8 +98,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
}; };
final private BundleHost bundleHost; private final BundleHost bundleHost;
final private RefreshingBundleHostListener refreshingBundleHostListener = private final RefreshingBundleHostListener refreshingBundleHostListener =
new RefreshingBundleHostListener(); new RefreshingBundleHostListener();
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) { GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) {
@@ -158,8 +157,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
}); });
scriptCategoryTree.getSelectionModel().setSelectionMode( scriptCategoryTree.getSelectionModel()
TreeSelectionModel.SINGLE_TREE_SELECTION); .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tableModel = new GhidraScriptTableModel(this, infoManager); tableModel = new GhidraScriptTableModel(this, infoManager);
@@ -237,7 +236,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
//================================================================================================== //==================================================================================================
public void readConfigState(SaveState saveState) { public void readConfigState(SaveState saveState) {
bundleHost.restoreStateAndActivate(saveState, getTool()); bundleHost.restoreManagedBundleState(saveState, getTool());
actionManager.restoreUserDefinedKeybindings(saveState); actionManager.restoreUserDefinedKeybindings(saveState);
actionManager.restoreScriptsThatAreInTool(saveState); actionManager.restoreScriptsThatAreInTool(saveState);
@@ -261,7 +260,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
bundleHost.saveState(saveState); bundleHost.saveManagedBundleState(saveState);
actionManager.saveUserDefinedKeybindings(saveState); actionManager.saveUserDefinedKeybindings(saveState);
actionManager.saveScriptsThatAreInTool(saveState); actionManager.saveScriptsThatAreInTool(saveState);
@@ -408,8 +407,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
action.setKeyBindingData(new KeyBindingData(ks)); action.setKeyBindingData(new KeyBindingData(ks));
} }
assert !infoManager.containsMetadata( assert !infoManager
renameFile) : "renamed script already has metadata"; .containsMetadata(renameFile) : "renamed script already has metadata";
infoManager.getScriptInfo(renameFile); infoManager.getScriptInfo(renameFile);
tableModel.switchScript(script, renameFile); tableModel.switchScript(script, renameFile);
@@ -452,15 +451,20 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
public List<ResourceFile> getScriptDirectories() { public List<ResourceFile> getScriptDirectories() {
return bundleHost.getGhidraBundles().stream().filter( return bundleHost.getGhidraBundles()
gb -> gb.isEnabled() && gb instanceof GhidraSourceBundle).map( .stream()
GhidraBundle::getPath).collect(Collectors.toList()); .filter(bundle -> bundle.isEnabled() && bundle instanceof GhidraSourceBundle)
.map(GhidraBundle::getPath)
.collect(Collectors.toList());
} }
public List<ResourceFile> getWritableScriptDirectories() { public List<ResourceFile> getWritableScriptDirectories() {
return bundleHost.getGhidraBundles().stream().filter( return bundleHost.getGhidraBundles()
GhidraSourceBundle.class::isInstance).filter(gb -> !gb.isSystemBundle()).map( .stream()
GhidraBundle::getPath).collect(Collectors.toList()); .filter(GhidraSourceBundle.class::isInstance)
.filter(bundle -> !bundle.isSystemBundle())
.map(GhidraBundle::getPath)
.collect(Collectors.toList());
} }
boolean isEditorOpen(ResourceFile script) { boolean isEditorOpen(ResourceFile script) {
@@ -698,14 +702,14 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
class RefreshingBundleHostListener implements BundleHostListener { class RefreshingBundleHostListener implements BundleHostListener {
@Override @Override
public void bundleBuilt(GhidraBundle sb, String summary) { public void bundleBuilt(GhidraBundle bundle, String summary) {
if (sb instanceof GhidraSourceBundle) { if (bundle instanceof GhidraSourceBundle) {
GhidraSourceBundle gsb = (GhidraSourceBundle) sb; GhidraSourceBundle sourceBundle = (GhidraSourceBundle) bundle;
for (ResourceFile sf : gsb.getNewSources()) { for (ResourceFile sourceFile : sourceBundle.getNewSources()) {
if (infoManager.containsMetadata(sf)) { if (infoManager.containsMetadata(sourceFile)) {
ScriptInfo info = infoManager.getExistingScriptInfo(sf); ScriptInfo scriptInfo = infoManager.getExistingScriptInfo(sourceFile);
BuildFailure e = gsb.getErrors(sf); GhidraBundle.BuildFailure e = sourceBundle.getErrors(sourceFile);
info.setCompileErrors(e != null); scriptInfo.setCompileErrors(e != null);
} }
} }
tableModel.fireTableDataChanged(); tableModel.fireTableDataChanged();
@@ -713,32 +717,32 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
@Override @Override
public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablment) { public void bundleEnablementChange(GhidraBundle bundle, boolean newEnablment) {
if (gbundle instanceof GhidraSourceBundle) { if (bundle instanceof GhidraSourceBundle) {
refresh(); refresh();
} }
} }
@Override @Override
public void bundleAdded(GhidraBundle gbundle) { public void bundleAdded(GhidraBundle bundle) {
plugin.getTool().setConfigChanged(true); plugin.getTool().setConfigChanged(true);
refresh(); refresh();
} }
@Override @Override
public void bundlesAdded(Collection<GhidraBundle> gbundles) { public void bundlesAdded(Collection<GhidraBundle> bundles) {
plugin.getTool().setConfigChanged(true); plugin.getTool().setConfigChanged(true);
refresh(); refresh();
} }
@Override @Override
public void bundleRemoved(GhidraBundle gbundle) { public void bundleRemoved(GhidraBundle bundle) {
plugin.getTool().setConfigChanged(true); plugin.getTool().setConfigChanged(true);
refresh(); refresh();
} }
@Override @Override
public void bundlesRemoved(Collection<GhidraBundle> gbundles) { public void bundlesRemoved(Collection<GhidraBundle> bundles) {
plugin.getTool().setConfigChanged(true); plugin.getTool().setConfigChanged(true);
refresh(); refresh();
} }
@@ -1030,10 +1034,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
private JComponent buildDescriptionComponent() { private JComponent buildDescriptionComponent() {
JPanel descriptionPanel = new JPanel(new BorderLayout());
descriptionTextPane = new JTextPane(); descriptionTextPane = new JTextPane();
descriptionTextPane.setEditable(false); descriptionTextPane.setEditable(false);
descriptionTextPane.setEditorKit(new HTMLEditorKit()); descriptionTextPane.setEditorKit(new HTMLEditorKit());
JPanel descriptionPanel = new JPanel(new BorderLayout());
descriptionPanel.add(descriptionTextPane); descriptionPanel.add(descriptionTextPane);
JScrollPane scrollPane = new JScrollPane(descriptionPanel); JScrollPane scrollPane = new JScrollPane(descriptionPanel);
@@ -50,10 +50,10 @@ import ghidra.util.task.TaskListener;
//@formatter:on //@formatter:on
public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService { public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService {
final private GhidraScriptComponentProvider provider; private final GhidraScriptComponentProvider provider;
static private int loaded = 0; private static int loaded = 0;
final private BundleHost bundleHost; private final BundleHost bundleHost;
public GhidraScriptMgrPlugin(PluginTool tool) { public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true); super(tool, true, true, true);
@@ -39,12 +39,12 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
private static final String EMPTY_STRING = ""; private static final String EMPTY_STRING = "";
private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON; private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON;
final static String SCRIPT_ACTION_COLUMN_NAME = "In Tool"; static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool";
final static String SCRIPT_STATUS_COLUMN_NAME = "Status"; static final String SCRIPT_STATUS_COLUMN_NAME = "Status";
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private List<ResourceFile> scriptList = new ArrayList<>(); private List<ResourceFile> scriptList = new ArrayList<>();
final private GhidraScriptInfoManager infoManager; private final GhidraScriptInfoManager infoManager;
GhidraScriptTableModel(GhidraScriptComponentProvider provider, GhidraScriptTableModel(GhidraScriptComponentProvider provider,
GhidraScriptInfoManager infoManager) { GhidraScriptInfoManager infoManager) {
@@ -74,12 +74,12 @@ public class GhidraScriptUtil {
setBundleHost(bundleHost); setBundleHost(bundleHost);
if (extraSystemPaths != null) { if (extraSystemPaths != null) {
for (String path : extraSystemPaths) { for (String path : extraSystemPaths) {
bundleHost.addGhidraBundle(new ResourceFile(path), true, true); bundleHost.add(new ResourceFile(path), true, true);
} }
} }
bundleHost.addGhidraBundle(GhidraScriptUtil.getUserScriptDirectory(), true, false); bundleHost.add(GhidraScriptUtil.getUserScriptDirectory(), true, false);
bundleHost.addGhidraBundles(GhidraScriptUtil.getSystemScriptPaths(), true, true); bundleHost.add(GhidraScriptUtil.getSystemScriptPaths(), true, true);
} }
public static void dispose() { public static void dispose() {
@@ -51,9 +51,9 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override @Override
public boolean deleteScript(ResourceFile sourceFile) { public boolean deleteScript(ResourceFile sourceFile) {
try { try {
Bundle b = getBundleForSource(sourceFile).getBundle(); Bundle osgiBundle = getBundleForSource(sourceFile).getOSGiBundle();
if (b != null) { if (osgiBundle != null) {
_bundleHost.deactivateSynchronously(b); _bundleHost.deactivateSynchronously(osgiBundle);
} }
} }
catch (GhidraBundleException | InterruptedException e) { catch (GhidraBundleException | InterruptedException e) {
@@ -95,7 +95,8 @@ public class JavaScriptProvider extends GhidraScriptProvider {
public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception { public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
GhidraSourceBundle gb = getBundleForSource(sourceFile); GhidraSourceBundle gb = getBundleForSource(sourceFile);
gb.build(writer); gb.build(writer);
Bundle b = _bundleHost.installFromLoc(gb.getBundleLoc());
Bundle b = _bundleHost.install(gb);
_bundleHost.activateSynchronously(b); _bundleHost.activateSynchronously(b);
@@ -26,6 +26,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
@@ -44,11 +45,9 @@ import docking.widgets.table.RowObjectTableModel;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.console.ConsoleComponentProvider; import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.plugin.core.osgi.GhidraSourceBundle;
import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.services.ConsoleService; import ghidra.app.services.ConsoleService;
import ghidra.framework.Application; import ghidra.framework.Application;
@@ -181,8 +180,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
static protected void wipe(Path path) throws IOException { static protected void wipe(Path path) throws IOException {
if (Files.exists(path)) { if (Files.exists(path)) {
for (Path p : (Iterable<Path>) Files.walk(path).sorted( for (Path p : (Iterable<Path>) Files.walk(path)
Comparator.reverseOrder())::iterator) { .sorted(Comparator.reverseOrder())::iterator) {
Files.deleteIfExists(p); Files.deleteIfExists(p);
} }
} }
@@ -687,8 +686,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
"Contents of file on disk do not match that of the editor after performing " + "Contents of file on disk do not match that of the editor after performing " +
"a save operation: " + file); "a save operation: " + file);
printChars(expectedContents, fileText); printChars(expectedContents, fileText);
Assert.fail( Assert
"Contents of file on disk do not match that of the editor after performing " + .fail("Contents of file on disk do not match that of the editor after performing " +
"a save operation: " + file); "a save operation: " + file);
} }
// //
@@ -980,15 +979,16 @@ public abstract class AbstractGhidraScriptMgrPluginTest
protected void cleanupOldTestFiles() throws IOException { protected void cleanupOldTestFiles() throws IOException {
// remove the compiled bundles directory so that any scripts we use will be recompiled // remove the compiled bundles directory so that any scripts we use will be recompiled
wipe(BundleHost.getCompiledBundlesDir()); wipe(GhidraSourceBundle.getCompiledBundlesDir());
String myTestName = super.testName.getMethodName(); String myTestName = super.testName.getMethodName();
// destroy any NewScriptxxx files...and Temp ones too // destroy any NewScriptxxx files...and Temp ones too
BundleStatusComponentProvider bundleStatusComponentProvider = List<ResourceFile> paths = provider.getBundleHost()
(BundleStatusComponentProvider) TestUtils.getInstanceField( .getBundlePaths()
"bundleStatusComponentProvider", provider); .stream()
List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths(); .filter(ResourceFile::isDirectory)
.collect(Collectors.toList());
for (ResourceFile path : paths) { for (ResourceFile path : paths) {
File file = path.getFile(false); File file = path.getFile(false);
@@ -55,7 +55,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
tmpdirs.add(tmpDir); tmpdirs.add(tmpDir);
ResourceFile rp = new ResourceFile(tmpDir.toFile()); ResourceFile rp = new ResourceFile(tmpDir.toFile());
current_gb = bundleHost.addGhidraBundle(rp, true, false); current_gb = bundleHost.add(rp, true, false);
gbstack.push(current_gb); gbstack.push(current_gb);
return current_gb; return current_gb;
} }
@@ -71,7 +71,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
@Before @Before
public void setup() throws OSGiException, IOException { public void setup() throws OSGiException, IOException {
wipe(BundleHost.getCompiledBundlesDir()); wipe(GhidraSourceBundle.getCompiledBundlesDir());
bundleHost = new BundleHost(); bundleHost = new BundleHost();
bundleHost.startFramework(); bundleHost.startFramework();
@@ -106,7 +106,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
} }
protected void activate() throws Exception { protected void activate() throws Exception {
Bundle bundle = bundleHost.installFromLoc(current_gb.getBundleLoc()); Bundle bundle = bundleHost.install(current_gb);
assertNotNull("failed to install bundle", bundle); assertNotNull("failed to install bundle", bundle);
bundle.start(); bundle.start();
} }
@@ -117,7 +117,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
} }
protected Class<?> loadClass(String classname) throws ClassNotFoundException { protected Class<?> loadClass(String classname) throws ClassNotFoundException {
Class<?> clazz = current_gb.getBundle().loadClass(classname); Class<?> clazz = current_gb.getOSGiBundle().loadClass(classname);
assertNotNull("failed to load class", clazz); assertNotNull("failed to load class", clazz);
return clazz; return clazz;
} }
@@ -313,7 +313,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
pushNewBundle(); pushNewBundle();
// @imports tag is only parsed from classes in default package // @importpackages tag is only parsed from classes in default package
addClass( addClass(
"//@importpackage lib\n" "//@importpackage lib\n"
, ,