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 {
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) {
bundleAdded(gbundle);
/**
* Invoked when a number of bundles is added at once. A listener should implement this method
* 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) {
bundleRemoved(gbundle);
/**
* Invoked when a number of bundles is removed at once. A listener should implement this method
* 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.
*
* 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> {
final Path path;
final GhidraBundle.Type type;
final String bundleLoc;
final String bundleLocation;
boolean active = 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;
public GhidraBundle.Type getType() {
return type;
}
BundleStatus(ResourceFile path, boolean enabled, boolean readonly, String bundleLoc) {
this.path = new Path(path, enabled, false, readonly);
type = GhidraBundle.getType(getPath());
this.bundleLoc = 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();
this.bundleLocation = bundleLoc;
}
@Override
@@ -96,12 +42,103 @@ public class BundleStatus implements Comparable<BundleStatus> {
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() {
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 {
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
*/
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 LessFreneticGTable bundleStatusTable;
@@ -60,9 +63,14 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
private GhidraFileFilter filter;
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) {
super(tool, "BundleManager", owner);
setHelpLocation(new HelpLocation("BundleManager", "BundleManager"));
@@ -136,12 +144,24 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
// to allow custom cell renderers
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;
int skinnyWidth = 60;
//
column = bundleStatusTable.getColumnModel().getColumn(
bundleStatusTableModel.enabledColumn.index);
column = bundleStatusTable.getColumnModel()
.getColumn(bundleStatusTableModel.enabledColumn.index);
column.setPreferredWidth(skinnyWidth);
column.setMinWidth(skinnyWidth);
column.setMaxWidth(skinnyWidth);
@@ -171,9 +191,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
setText("");
}
return x;
}
});
//
@@ -197,82 +215,63 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
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);
scrollPane.getViewport().setBackground(bundleStatusTable.getBackground());
@Override
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() {
DockingAction action;
//
action = new DockingAction("ActivateBundles", this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
doActivateBundles();
}
addBundlesAction("ActivateBundles", "Activate bundle(s)",
ResourceManager.loadImage("images/media-playback-start.png"), this::doActivateBundles);
@Override
public boolean isEnabledForContext(ActionContext context) {
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("DeactivateBundles", "Deactivate bundle(s)",
ResourceManager.loadImage("images/media-playback-stop.png"), this::doDeactivateBundles);
addBundlesAction("CleanBundles", "Clean bundle(s)",
ResourceManager.loadImage("images/erase16.png"), this::doClean);
//
action = new DockingAction("DeactivateBundles", this.getName()) {
@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()) {
@Override
public void actionPerformed(ActionContext context) {
@@ -285,16 +284,16 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
};
action.setPopupMenuData(new MenuData(new String[] { "Add bundle(s)" },
ResourceManager.loadImage("images/Plus.png"), BUNDLE_LIST_GROUP));
action.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/Plus.png"), BUNDLE_LIST_GROUP));
Icon icon = ResourceManager.loadImage("images/Plus.png");
action.setPopupMenuData(
new MenuData(new String[] { "Add bundle(s)" }, icon, BUNDLE_LIST_GROUP));
action.setToolBarData(new ToolBarData(icon, BUNDLE_LIST_GROUP));
action.setDescription("Display file chooser to add bundles to list");
action.setEnabled(true);
getTool().addLocalAction(this, action);
//
//
icon = ResourceManager.loadImage("images/edit-delete.png");
action = new DockingAction("RemoveBundles", this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
@@ -307,11 +306,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
};
action.setPopupMenuData(new MenuData(new String[] { "Remove bundle(s)" },
ResourceManager.loadImage("images/edit-delete.png"), BUNDLE_LIST_GROUP));
action.setToolBarData(new ToolBarData(ResourceManager.loadImage("images/edit-delete.png"),
BUNDLE_LIST_GROUP));
action.setPopupMenuData(
new MenuData(new String[] { "Remove bundle(s)" }, icon, BUNDLE_LIST_GROUP));
action.setToolBarData(new ToolBarData(icon, BUNDLE_LIST_GROUP));
action.setDescription("Remove selected bundle(s) from the list");
action.setEnabled(true);
getTool().addLocalAction(this, action);
@@ -341,7 +338,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
}
if (anythingCleaned) {
getModel().fireTableDataChanged();
bundleStatusTableModel.fireTableDataChanged();
AnimationUtils.shakeComponent(getComponent());
}
}
@@ -354,9 +351,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
doDeactivateBundles();
Map<Boolean, List<GhidraBundle>> bundles =
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().map(
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect(
Collectors.partitioningBy(gb -> gb.isSystemBundle()));
bundleStatusTableModel.getRowObjects(selectedModelRows)
.stream()
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getPath()))
.collect(Collectors.partitioningBy(gb -> gb.isSystemBundle()));
List<GhidraBundle> systemBundles = bundles.get(true);
if (!systemBundles.isEmpty()) {
StringBuilder sb = new StringBuilder();
@@ -388,19 +386,19 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
@Override
public boolean accept(File f, GhidraFileChooserModel l_model) {
return filter.accept(f, l_model);
public boolean accept(File f, GhidraFileChooserModel model) {
return filter.accept(f, model);
}
});
}
String lastSelected = Preferences.getProperty(preferenceForLastSelectedBundle);
String lastSelected = Preferences.getProperty(PREFENCE_LAST_SELECTED_BUNDLE);
if (lastSelected != null) {
File f = new File(lastSelected);
fileChooser.setSelectedFile(f);
}
}
else {
String lastSelected = Preferences.getProperty(preferenceForLastSelectedBundle);
String lastSelected = Preferences.getProperty(PREFENCE_LAST_SELECTED_BUNDLE);
if (lastSelected != null) {
File f = new File(lastSelected);
fileChooser.setSelectedFile(f);
@@ -410,10 +408,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
List<File> files = fileChooser.getSelectedFiles();
if (!files.isEmpty()) {
Preferences.setProperty(preferenceForLastSelectedBundle,
files.get(0).getAbsolutePath());
Preferences.setProperty(PREFENCE_LAST_SELECTED_BUNDLE, files.get(0).getAbsolutePath());
bundleHost.addGhidraBundles(
bundleHost.add(
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()),
true, false);
}
@@ -429,8 +426,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
bundleStatusTable.chill();
List<BundleStatus> statuses =
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter(
bs -> !bs.isActive()).collect(Collectors.toUnmodifiableList());
bundleStatusTableModel.getRowObjects(selectedModelRows)
.stream()
.filter(bs -> !bs.isActive())
.collect(Collectors.toUnmodifiableList());
List<GhidraBundle> gbs = new ArrayList<>();
for (BundleStatus bs : statuses) {
@@ -469,16 +468,16 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
new TaskLauncher(new Task("deactivating", true, true, false) {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
List<GhidraBundle> gbs =
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter(
bs -> bs.isActive()).map(
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect(
Collectors.toList());
List<GhidraBundle> gbs = bundleStatusTableModel.getRowObjects(selectedModelRows)
.stream()
.filter(bs -> bs.isActive())
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getPath()))
.collect(Collectors.toList());
monitor.setMaximum(gbs.size());
for (GhidraBundle gb : gbs) {
try {
bundleHost.deactivateSynchronously(gb.getBundleLoc());
bundleHost.deactivateSynchronously(gb.getBundleLocation());
}
catch (GhidraBundleException | InterruptedException e) {
e.printStackTrace(console.getStdErr());
@@ -501,10 +500,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
GhidraBundle gb = bundleHost.getExistingGhidraBundle(status.getPath());
if (activate) {
gb.build(console.getStdErr());
bundleHost.activateSynchronously(gb.getBundleLoc());
bundleHost.activateSynchronously(gb.getBundleLocation());
}
else { // deactivate
bundleHost.deactivateSynchronously(gb.getBundleLoc());
bundleHost.deactivateSynchronously(gb.getBundleLocation());
}
}
catch (Exception e) {
@@ -518,18 +517,14 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}, null, 1000);
}
public BundleStatusTableModel getModel() {
return bundleStatusTableModel;
}
public void notifyTableRowChanged(BundleStatus status) {
private void notifyTableRowChanged(BundleStatus status) {
int modelRowIndex = bundleStatusTableModel.getRowIndex(status);
int viewRowIndex = filterPanel.getViewRow(modelRowIndex);
bundleStatusTable.notifyTableChanged(
new TableModelEvent(bundleStatusTableModel, viewRowIndex));
bundleStatusTable
.notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex));
}
public void notifyTableDataChanged() {
private void notifyTableDataChanged() {
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel));
}
@@ -545,6 +540,9 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
*/
/**
* cleanup this component
*/
public void dispose() {
bundleStatusTable.dispose();
}
@@ -553,4 +551,17 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
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) {
@Override
boolean editable(BundleStatus status) {
return status.pathExists(); // XXX maybe only if it's already enabled
return status.pathExists() && status.isEnabled();
}
@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;
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) {
return getStatusFromLoc(gb.getBundleLoc());
}
BundleStatus getStatusFromLoc(String bundleLoc) {
BundleStatus status = loc2status.get(bundleLoc);
if (status == null) {
Msg.showError(BundleStatusTableModel.this, provider.getComponent(),
"bundle status error", "bundle has no status!");
protected class MyBundleHostListener implements BundleHostListener {
@Override
public void bundleBuilt(GhidraBundle bundle, String summary) {
BundleStatus status = getStatus(bundle);
status.setSummary(summary);
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
@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) {
@@ -142,114 +191,57 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
this.provider = provider;
this.bundleHost = bundleHost;
statuses = new ArrayList<>();
for (GhidraBundle gb : bundleHost.getGhidraBundles()) {
addNewStatus(gb);
for (GhidraBundle bundle : bundleHost.getGhidraBundles()) {
addNewStatus(bundle);
}
bundleHost.addListener(bundleListener = new BundleHostListener() {
@Override
public void bundleBuilt(GhidraBundle gb, String summary) {
BundleStatus status = getStatus(gb);
status.setSummary(summary);
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
bundleHost.addListener(bundleHostListener = new MyBundleHostListener());
}
@Override
public void bundleActivationChange(GhidraBundle gb, boolean newActivation) {
BundleStatus status = getStatus(gb);
int row = getRowIndex(status);
status.setBusy(false);
if (newActivation) {
status.setActive(true);
}
else {
status.setActive(false);
}
fireTableRowsUpdated(row, row);
}
Column getColumn(int i) {
if (i >= 0 && i < columns.size()) {
return columns.get(i);
}
return badColumn;
}
@Override
public void bundleAdded(GhidraBundle gb) {
addNewStatus(gb);
}
BundleStatus getStatus(GhidraBundle bundle) {
return getStatusFromLoc(bundle.getBundleLocation());
}
@Override
public void bundlesAdded(Collection<GhidraBundle> gbundles) {
int index = statuses.size();
for (GhidraBundle gb : gbundles) {
addNewStatusNoFire(gb);
}
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
public void bundleException(GhidraBundleException gbe) {
BundleStatus status = getStatusFromLoc(gbe.getBundleLocation());
status.setSummary(gbe.getMessage());
int row = getRowIndex(status);
fireTableRowsUpdated(row, row);
}
});
BundleStatus getStatusFromLoc(String bundleLoc) {
BundleStatus status = bundleLocToStatusMap.get(bundleLoc);
if (status == null) {
Msg.showError(BundleStatusTableModel.this, provider.getComponent(),
"bundle status error", "bundle has no status!");
}
return status;
}
@Override
public void dispose() {
super.dispose();
bundleHost.removeListener(bundleListener);
bundleHost.removeListener(bundleHostListener);
}
public List<ResourceFile> getEnabledPaths() {
List<ResourceFile> list = new ArrayList<>();
for (BundleStatus status : statuses) {
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());
private void addNewStatusNoFire(GhidraBundle bundle) {
BundleStatus status = new BundleStatus(bundle.getPath(), bundle.isEnabled(),
bundle.isSystemBundle(), bundle.getBundleLocation());
if (statuses.contains(status)) {
throw new RuntimeException(
"Bundle status manager already contains " + gb.getPath().toString());
"Bundle status manager already contains " + bundle.getPath().toString());
}
status.setActive(gb.isActive());
loc2status.put(status.getBundleLoc(), status);
status.setActive(bundle.isActive());
bundleLocToStatusMap.put(status.getBundleLocation(), status);
statuses.add(status);
}
/**
* add new status and fire a table update
*/
private void addNewStatus(GhidraBundle gb) {
private void addNewStatus(GhidraBundle bundle) {
int index = statuses.size();
addNewStatusNoFire(gb);
addNewStatusNoFire(bundle);
fireTableRowsInserted(index, index);
}
@@ -257,7 +249,7 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
if (!status.isReadOnly()) {
int i = statuses.indexOf(status);
statuses.remove(i);
loc2status.remove(status.getBundleLoc());
bundleLocToStatusMap.remove(status.getBundleLocation());
return i;
}
return -1;
@@ -271,8 +263,9 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
}
void remove(int[] modelRows) {
List<BundleStatus> toRemove = Arrays.stream(modelRows).mapToObj(statuses::get).collect(
Collectors.toUnmodifiableList());
List<BundleStatus> toRemove = Arrays.stream(modelRows)
.mapToObj(statuses::get)
.collect(Collectors.toUnmodifiableList());
removeStatuses(toRemove);
}
@@ -315,7 +308,7 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
BundleStatus status = statuses.get(rowIndex);
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);
}
@@ -339,8 +332,19 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
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) {
synchronized (bundleStatusListeners) {
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) {
synchronized (bundleStatusListeners) {
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) {
List<BundleStatus> rows = new ArrayList<>(modelRowIndices.length);
for (int i : modelRowIndices) {
@@ -418,24 +433,10 @@ public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatu
* (re)compute cached mapping from bundleloc to bundlepath
*/
private void computeCache() {
loc2status.clear();
bundleLocToStatusMap.clear();
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;
/**
* Proxy for an OSGi bundle that may require being built.
*/
public abstract class GhidraBundle {
protected final ResourceFile path;
@@ -31,9 +34,10 @@ public abstract class GhidraBundle {
protected boolean enabled;
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.path = path;
this.path = bundlePath;
this.enabled = enabled;
this.systemBundle = systemBundle;
}
@@ -48,45 +52,97 @@ public abstract class GhidraBundle {
/**
* 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
* @throws Exception sorry, wasn't possible
* @throws Exception if the build cannot complete
*/
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 {
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() {
return path;
}
/**
* @return true if this bundle is enabled
*/
public boolean isEnabled() {
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;
}
/**
* 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() {
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 {
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;
}
String n = rf.getName().toLowerCase();
String n = path.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
@@ -96,11 +152,17 @@ public abstract class GhidraBundle {
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;
}
String n = f.getName().toLowerCase();
String n = path.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
@@ -110,21 +172,20 @@ public abstract class GhidraBundle {
return GhidraBundle.Type.INVALID;
}
public Bundle getBundle() {
return bundleHost.getBundle(getBundleLoc());
}
public void activate() throws Exception {
activate(new PrintWriter(System.err));
}
public void activate(PrintWriter writer) throws Exception {
build(writer);
bundleHost.activateSynchronously(getBundleLoc());
/**
* Get the OSGi bundle respresented by this GhidraBundle or null
*
* @return a Bundle or null
*/
public Bundle getOSGiBundle() {
return bundleHost.getOSGiBundle(getBundleLocation());
}
/**
* @return true if this bundle is active
*/
public boolean isActive() {
Bundle b = getBundle();
Bundle b = getOSGiBundle();
return (b != null) && b.getState() == Bundle.ACTIVE;
}
@@ -19,18 +19,18 @@ import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
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
final public void start(BundleContext bc) throws Exception {
start(bc, null);
public final void start(BundleContext bundleContext) throws Exception {
start(bundleContext, null);
}
@Override
final public void stop(BundleContext bc) throws Exception {
stop(bc, null);
public final void stop(BundleContext bundleContext) throws Exception {
stop(bundleContext, null);
}
}
@@ -21,28 +21,52 @@ import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
public class GhidraBundleException extends OSGiException {
private Bundle bundle;
private String bundle_loc;
private final Bundle bundle;
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) {
super(msg + ": " + parsedCause(cause), cause);
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);
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() {
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() {
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) {
return "";
}
@@ -81,9 +105,10 @@ public class GhidraBundleException extends OSGiException {
return message;
}
// parse the package constraints from filters in the BundleRequirement string
String packages =
OSGiUtils.extractPackages(be.getMessage()).stream().distinct().collect(
Collectors.joining("\n"));
String packages = OSGiUtils.extractPackageNamesFromFailedResolution(be.getMessage())
.stream()
.distinct()
.collect(Collectors.joining("\n"));
return "RESOLVE_ERROR with reference to packages:\n" + packages;
}
@@ -26,13 +26,24 @@ import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Jar;
import generic.jar.ResourceFile;
/**
* Proxy to an ordinary OSGi Jar bundle. {@link GhidraJarBundle#build(PrintWriter)} does nothing.
*/
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,
boolean systemBundle) {
super(bundleHost, path, enabled, systemBundle);
this.bundleLoc = "file://" + path.getAbsolutePath().toString();
this.bundleLocation = "file://" + path.getAbsolutePath().toString();
}
@Override
@@ -46,19 +57,19 @@ public class GhidraJarBundle extends GhidraBundle {
}
@Override
public String getBundleLoc() {
return bundleLoc;
public String getBundleLocation() {
return bundleLocation;
}
@Override
public List<BundleRequirement> getAllReqs() {
public List<BundleRequirement> getAllRequirements() {
Jar jar;
try {
jar = new Jar(path.getFile(true));
Manifest m = jar.getManifest();
String imps = m.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
if (imps != null) {
return BundleHost.parseImports(imps);
return OSGiUtils.parseImports(imps);
}
return Collections.emptyList();
}
@@ -23,11 +23,14 @@ import org.osgi.framework.wiring.BundleRequirement;
import generic.jar.ResourceFile;
/**
* {@link GhidraPlaceholderBundle} represents invalid bundle paths in the GUI.
*/
public class GhidraPlaceholderBundle extends GhidraBundle {
GhidraPlaceholderBundle(BundleHost bundleHost, ResourceFile path, boolean enabled,
boolean systemBundle) {
super(bundleHost, path, enabled, systemBundle);
GhidraPlaceholderBundle(BundleHost bundleHost, ResourceFile bundlePath, boolean isEnabled,
boolean isSystemBundle) {
super(bundleHost, bundlePath, isEnabled, isSystemBundle);
}
@Override
@@ -41,12 +44,12 @@ public class GhidraPlaceholderBundle extends GhidraBundle {
}
@Override
public String getBundleLoc() {
public String getBundleLocation() {
return "invalid://" + getPath();
}
@Override
List<BundleRequirement> getAllReqs() {
List<BundleRequirement> getAllRequirements() {
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
* 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 {
boolean chilled = false;
@@ -35,27 +35,29 @@ class LessFreneticGTable extends GTable {
}
@Override
public void tableChanged(TableModelEvent e) {
public void tableChanged(TableModelEvent event) {
if (!chilled) {
super.tableChanged(e);
super.tableChanged(event);
}
}
}
LessFreneticGTable(TableModel model) {
super(model);
}
/** suppress issuing table change events */
public void chill() {
chilled = true;
}
/** resume issuing table change events */
public void thaw() {
chilled = false;
notifyTableChanged(new TableModelEvent(getModel()));
}
LessFreneticGTable(TableModel dm) {
super(dm);
}
@SuppressWarnings("unchecked")
@Override
protected <T> SelectionManager createSelectionManager(TableModel model) {
@@ -14,15 +14,26 @@
* limitations under the License.
*/
package ghidra.app.plugin.core.osgi;
import ghidra.util.exception.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;
import java.util.List;
import java.util.Scanner;
import java.io.File;
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.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 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 m.group(1);
}).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() {
URI URI = entryFile.toURI();
URL URL = null;
URI uri = entryFile.toURI();
URL url = null;
try {
URL = URI.toURL();
url = uri.toURL();
}
catch (MalformedURLException e) {
// shouldn't happen
@@ -591,7 +591,7 @@ class GhidraScriptActionManager {
return;
}
BrowserLoader.display(URL, URL, plugin.getTool());
BrowserLoader.display(url, url, plugin.getTool());
}
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 generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.*;
import ghidra.app.plugin.core.osgi.BundleHost.BuildFailure;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.framework.options.SaveState;
@@ -66,12 +65,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
static final String WINDOW_GROUP = "Script Group";
private Map<ResourceFile, GhidraScriptEditorComponentProvider> editorMap = new HashMap<>();
final private GhidraScriptMgrPlugin plugin;
private final GhidraScriptMgrPlugin plugin;
private JPanel component;
private RootNode scriptRoot;
private GTree scriptCategoryTree;
private DraggableScriptTable scriptTable;
final private GhidraScriptInfoManager infoManager;
private final GhidraScriptInfoManager infoManager;
private GhidraScriptTableModel tableModel;
private BundleStatusComponentProvider bundleStatusComponentProvider;
private TaskListener taskListener = new ScriptTaskListener();
@@ -99,8 +98,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
};
final private BundleHost bundleHost;
final private RefreshingBundleHostListener refreshingBundleHostListener =
private final BundleHost bundleHost;
private final RefreshingBundleHostListener refreshingBundleHostListener =
new RefreshingBundleHostListener();
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) {
@@ -158,8 +157,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
});
scriptCategoryTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
scriptCategoryTree.getSelectionModel()
.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tableModel = new GhidraScriptTableModel(this, infoManager);
@@ -237,7 +236,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
//==================================================================================================
public void readConfigState(SaveState saveState) {
bundleHost.restoreStateAndActivate(saveState, getTool());
bundleHost.restoreManagedBundleState(saveState, getTool());
actionManager.restoreUserDefinedKeybindings(saveState);
actionManager.restoreScriptsThatAreInTool(saveState);
@@ -261,7 +260,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
public void writeConfigState(SaveState saveState) {
bundleHost.saveState(saveState);
bundleHost.saveManagedBundleState(saveState);
actionManager.saveUserDefinedKeybindings(saveState);
actionManager.saveScriptsThatAreInTool(saveState);
@@ -408,8 +407,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
action.setKeyBindingData(new KeyBindingData(ks));
}
assert !infoManager.containsMetadata(
renameFile) : "renamed script already has metadata";
assert !infoManager
.containsMetadata(renameFile) : "renamed script already has metadata";
infoManager.getScriptInfo(renameFile);
tableModel.switchScript(script, renameFile);
@@ -452,15 +451,20 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
public List<ResourceFile> getScriptDirectories() {
return bundleHost.getGhidraBundles().stream().filter(
gb -> gb.isEnabled() && gb instanceof GhidraSourceBundle).map(
GhidraBundle::getPath).collect(Collectors.toList());
return bundleHost.getGhidraBundles()
.stream()
.filter(bundle -> bundle.isEnabled() && bundle instanceof GhidraSourceBundle)
.map(GhidraBundle::getPath)
.collect(Collectors.toList());
}
public List<ResourceFile> getWritableScriptDirectories() {
return bundleHost.getGhidraBundles().stream().filter(
GhidraSourceBundle.class::isInstance).filter(gb -> !gb.isSystemBundle()).map(
GhidraBundle::getPath).collect(Collectors.toList());
return bundleHost.getGhidraBundles()
.stream()
.filter(GhidraSourceBundle.class::isInstance)
.filter(bundle -> !bundle.isSystemBundle())
.map(GhidraBundle::getPath)
.collect(Collectors.toList());
}
boolean isEditorOpen(ResourceFile script) {
@@ -698,14 +702,14 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
class RefreshingBundleHostListener implements BundleHostListener {
@Override
public void bundleBuilt(GhidraBundle sb, String summary) {
if (sb instanceof GhidraSourceBundle) {
GhidraSourceBundle gsb = (GhidraSourceBundle) sb;
for (ResourceFile sf : gsb.getNewSources()) {
if (infoManager.containsMetadata(sf)) {
ScriptInfo info = infoManager.getExistingScriptInfo(sf);
BuildFailure e = gsb.getErrors(sf);
info.setCompileErrors(e != null);
public void bundleBuilt(GhidraBundle bundle, String summary) {
if (bundle instanceof GhidraSourceBundle) {
GhidraSourceBundle sourceBundle = (GhidraSourceBundle) bundle;
for (ResourceFile sourceFile : sourceBundle.getNewSources()) {
if (infoManager.containsMetadata(sourceFile)) {
ScriptInfo scriptInfo = infoManager.getExistingScriptInfo(sourceFile);
GhidraBundle.BuildFailure e = sourceBundle.getErrors(sourceFile);
scriptInfo.setCompileErrors(e != null);
}
}
tableModel.fireTableDataChanged();
@@ -713,32 +717,32 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
@Override
public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablment) {
if (gbundle instanceof GhidraSourceBundle) {
public void bundleEnablementChange(GhidraBundle bundle, boolean newEnablment) {
if (bundle instanceof GhidraSourceBundle) {
refresh();
}
}
@Override
public void bundleAdded(GhidraBundle gbundle) {
public void bundleAdded(GhidraBundle bundle) {
plugin.getTool().setConfigChanged(true);
refresh();
}
@Override
public void bundlesAdded(Collection<GhidraBundle> gbundles) {
public void bundlesAdded(Collection<GhidraBundle> bundles) {
plugin.getTool().setConfigChanged(true);
refresh();
}
@Override
public void bundleRemoved(GhidraBundle gbundle) {
public void bundleRemoved(GhidraBundle bundle) {
plugin.getTool().setConfigChanged(true);
refresh();
}
@Override
public void bundlesRemoved(Collection<GhidraBundle> gbundles) {
public void bundlesRemoved(Collection<GhidraBundle> bundles) {
plugin.getTool().setConfigChanged(true);
refresh();
}
@@ -1030,10 +1034,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
private JComponent buildDescriptionComponent() {
JPanel descriptionPanel = new JPanel(new BorderLayout());
descriptionTextPane = new JTextPane();
descriptionTextPane.setEditable(false);
descriptionTextPane.setEditorKit(new HTMLEditorKit());
JPanel descriptionPanel = new JPanel(new BorderLayout());
descriptionPanel.add(descriptionTextPane);
JScrollPane scrollPane = new JScrollPane(descriptionPanel);
@@ -50,10 +50,10 @@ import ghidra.util.task.TaskListener;
//@formatter:on
public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService {
final private GhidraScriptComponentProvider provider;
private final GhidraScriptComponentProvider provider;
static private int loaded = 0;
final private BundleHost bundleHost;
private static int loaded = 0;
private final BundleHost bundleHost;
public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true);
@@ -39,12 +39,12 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
private static final String EMPTY_STRING = "";
private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON;
final static String SCRIPT_ACTION_COLUMN_NAME = "In Tool";
final static String SCRIPT_STATUS_COLUMN_NAME = "Status";
static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool";
static final String SCRIPT_STATUS_COLUMN_NAME = "Status";
private GhidraScriptComponentProvider provider;
private List<ResourceFile> scriptList = new ArrayList<>();
final private GhidraScriptInfoManager infoManager;
private final GhidraScriptInfoManager infoManager;
GhidraScriptTableModel(GhidraScriptComponentProvider provider,
GhidraScriptInfoManager infoManager) {
@@ -74,12 +74,12 @@ public class GhidraScriptUtil {
setBundleHost(bundleHost);
if (extraSystemPaths != null) {
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.addGhidraBundles(GhidraScriptUtil.getSystemScriptPaths(), true, true);
bundleHost.add(GhidraScriptUtil.getUserScriptDirectory(), true, false);
bundleHost.add(GhidraScriptUtil.getSystemScriptPaths(), true, true);
}
public static void dispose() {
@@ -51,9 +51,9 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override
public boolean deleteScript(ResourceFile sourceFile) {
try {
Bundle b = getBundleForSource(sourceFile).getBundle();
if (b != null) {
_bundleHost.deactivateSynchronously(b);
Bundle osgiBundle = getBundleForSource(sourceFile).getOSGiBundle();
if (osgiBundle != null) {
_bundleHost.deactivateSynchronously(osgiBundle);
}
}
catch (GhidraBundleException | InterruptedException e) {
@@ -95,7 +95,8 @@ public class JavaScriptProvider extends GhidraScriptProvider {
public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
GhidraSourceBundle gb = getBundleForSource(sourceFile);
gb.build(writer);
Bundle b = _bundleHost.installFromLoc(gb.getBundleLoc());
Bundle b = _bundleHost.install(gb);
_bundleHost.activateSynchronously(b);
@@ -26,6 +26,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.table.TableModel;
@@ -44,11 +45,9 @@ import docking.widgets.table.RowObjectTableModel;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
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.BundleHost;
import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider;
import ghidra.app.plugin.core.osgi.GhidraSourceBundle;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.framework.Application;
@@ -181,8 +180,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
static protected void wipe(Path path) throws IOException {
if (Files.exists(path)) {
for (Path p : (Iterable<Path>) Files.walk(path).sorted(
Comparator.reverseOrder())::iterator) {
for (Path p : (Iterable<Path>) Files.walk(path)
.sorted(Comparator.reverseOrder())::iterator) {
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 " +
"a save operation: " + file);
printChars(expectedContents, fileText);
Assert.fail(
"Contents of file on disk do not match that of the editor after performing " +
Assert
.fail("Contents of file on disk do not match that of the editor after performing " +
"a save operation: " + file);
}
//
@@ -980,15 +979,16 @@ public abstract class AbstractGhidraScriptMgrPluginTest
protected void cleanupOldTestFiles() throws IOException {
// 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();
// destroy any NewScriptxxx files...and Temp ones too
BundleStatusComponentProvider bundleStatusComponentProvider =
(BundleStatusComponentProvider) TestUtils.getInstanceField(
"bundleStatusComponentProvider", provider);
List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths();
List<ResourceFile> paths = provider.getBundleHost()
.getBundlePaths()
.stream()
.filter(ResourceFile::isDirectory)
.collect(Collectors.toList());
for (ResourceFile path : paths) {
File file = path.getFile(false);
@@ -55,7 +55,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
tmpdirs.add(tmpDir);
ResourceFile rp = new ResourceFile(tmpDir.toFile());
current_gb = bundleHost.addGhidraBundle(rp, true, false);
current_gb = bundleHost.add(rp, true, false);
gbstack.push(current_gb);
return current_gb;
}
@@ -71,7 +71,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
@Before
public void setup() throws OSGiException, IOException {
wipe(BundleHost.getCompiledBundlesDir());
wipe(GhidraSourceBundle.getCompiledBundlesDir());
bundleHost = new BundleHost();
bundleHost.startFramework();
@@ -106,7 +106,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
}
protected void activate() throws Exception {
Bundle bundle = bundleHost.installFromLoc(current_gb.getBundleLoc());
Bundle bundle = bundleHost.install(current_gb);
assertNotNull("failed to install bundle", bundle);
bundle.start();
}
@@ -117,7 +117,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
}
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);
return clazz;
}
@@ -313,7 +313,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
pushNewBundle();
// @imports tag is only parsed from classes in default package
// @importpackages tag is only parsed from classes in default package
addClass(
"//@importpackage lib\n"
,