packages = new HashSet<>();
OSGiUtils.getPackagesFromClasspath(packages);
return packages.stream().collect(Collectors.joining(","));
@@ -387,11 +359,11 @@ public class BundleHost {
}
/**
- * A directory for use by the OSGi framework as a cache
+ * A directory for use by the OSGi framework as a cache.
*
* @return the directory
*/
- protected static Path getCacheDir() {
+ private static Path getCacheDir() {
return BundleHost.getOsgiDir().resolve("felixcache");
}
@@ -401,16 +373,19 @@ public class BundleHost {
return cacheDir.toAbsolutePath().toString();
}
- protected void createAndConfigureFramework() throws IOException {
+ private void createAndConfigureFramework() throws IOException {
Properties config = new Properties();
// allow multiple bundles w/ the same symbolic name -- location can distinguish
config.setProperty(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MULTIPLE);
- // use the default, inferred from environment
- // config.setProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES,"osgi.ee; osgi.ee=\"JavaSE\";version:List=\"...\"");
- // compute and add everything in the class path. extra packages have lower precedence than imports,
- // so an Import-Package / @importpackage will override the "living off the land" default
+ // use the default, inferred from environment
+ // config.setProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES,
+ // "osgi.ee; osgi.ee=\"JavaSE\";version:List=\"...\"");
+
+ // compute and add everything in the class path. extra packages have lower precedence than
+ // imports, so an Import-Package / @importpackage will override the "living off the land"
+ // default
config.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, buildExtraSystemPackages());
// only clean on first startup, o/w keep our storage around
@@ -423,41 +398,43 @@ public class BundleHost {
config.put(FelixConstants.LOG_LEVEL_PROP, "1");
if (STDERR_DEBUGGING) {
config.put(FelixConstants.LOG_LEVEL_PROP, "999");
- // config.put(FelixConstants.LOG_LOGGER_PROP, new org.apache.felix.framework.Logger() {...});
+ // config.put(FelixConstants.LOG_LOGGER_PROP,
+ // new org.apache.felix.framework.Logger() {...});
}
FrameworkFactory factory = new FrameworkFactory();
felixFramework = factory.newFramework(config);
}
- protected void addDebuggingListeners() {
- frameworkBundleContext.addFrameworkListener(new FrameworkListener() {
- @Override
- public void frameworkEvent(FrameworkEvent event) {
- System.err.printf("%s %s\n", event.getBundle(), event);
+ private void addDebuggingListeners() {
+ if (!STDERR_DEBUGGING) {
+ return;
+ }
+
+ frameworkBundleContext.addFrameworkListener(
+ event -> {
+ String msg = String.format("AA: %s %s\n", event.getBundle(), event);
+ Msg.debug(this, msg);
+ });
+
+ frameworkBundleContext.addServiceListener(event -> {
+
+ String type = "?";
+ if (event.getType() == ServiceEvent.REGISTERED) {
+ type = "registered";
}
- });
- frameworkBundleContext.addServiceListener(new ServiceListener() {
- @Override
- public void serviceChanged(ServiceEvent event) {
-
- String type = "?";
- if (event.getType() == ServiceEvent.REGISTERED) {
- type = "registered";
- }
- else if (event.getType() == ServiceEvent.UNREGISTERING) {
- type = "unregistering";
- }
-
- System.err.printf("%s %s from %s\n", event.getSource(), type,
- event.getServiceReference().getBundle().getLocation());
-
+ else if (event.getType() == ServiceEvent.UNREGISTERING) {
+ type = "unregistering";
}
+
+ String msg = String.format("BB: %s %s from %s\n", event.getSource(), type,
+ event.getServiceReference().getBundle().getLocation());
+ Msg.debug(this, msg);
});
}
/**
- * start the framework
+ * Start the framework.
*
* @throws OSGiException framework failures
* @throws IOException filesystem setup
@@ -469,48 +446,53 @@ public class BundleHost {
felixFramework.init();
}
catch (BundleException e) {
- throw new OSGiException("initializing felix OSGi framework", e);
+ throw new OSGiException("Exception initializing felix OSGi framework", e);
}
+
frameworkBundleContext = felixFramework.getBundleContext();
- if (STDERR_DEBUGGING) {
- addDebuggingListeners();
- }
+ addDebuggingListeners();
- frameworkBundleContext
- .addBundleListener(new MyBundleListener(frameworkBundleContext.getBundle()));
+ Bundle bundle = frameworkBundleContext.getBundle();
+ frameworkBundleContext.addBundleListener(new MyBundleListener(bundle));
try {
felixFramework.start();
}
catch (BundleException e) {
- throw new OSGiException("starting felix OSGi framework", e);
+ throw new OSGiException("Exception starting felix OSGi framework", e);
}
}
/**
- * stop the OSGi framework synchronously
+ * Stop the OSGi framework.
+ *
+ * This may wait for up to 5 seconds for the framework to fully stop. If that timeout
+ * passes an error will be logged.
*/
- protected void stopFramework() {
- if (felixFramework != null) {
- try {
- felixFramework.stop();
- // any bundles that linger after a few seconds might be the source
- // of subtle problems, so wait for them to stop and report any problems.
- FrameworkEvent event = felixFramework.waitForStop(5000);
- if (event.getType() == FrameworkEvent.WAIT_TIMEDOUT) {
- Msg.error(this, "Stopping OSGi framework timed out after 5 seconds.");
- }
- felixFramework = null;
- }
- catch (BundleException | InterruptedException e) {
- Msg.error(this, "Failed to stop OSGi framework.");
- e.printStackTrace();
+ public void stopFramework() {
+ if (felixFramework == null) {
+ return;
+ }
+
+ try {
+ felixFramework.stop();
+
+ // any bundles that linger after a few seconds might be the source of subtle problems,
+ // so wait for them to stop and report any problems.
+ FrameworkEvent event = felixFramework.waitForStop(5000);
+ if (event.getType() == FrameworkEvent.WAIT_TIMEDOUT) {
+ Msg.error(this, "Stopping OSGi framework timed-out after 5 seconds.");
}
+ felixFramework = null;
+ }
+ catch (BundleException | InterruptedException e) {
+ Msg.error(this, "Failed to stop OSGi framework.", e);
}
}
/**
+ * Gets the host framework.
* @return the OSGi framework
*/
Framework getHostFramework() {
@@ -565,7 +547,8 @@ public class BundleHost {
}
/**
- * Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED" state.
+ * Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED"
+ * state.
*
* @param bundle the bundle
* @throws GhidraBundleException if there's a problem activating
@@ -575,7 +558,7 @@ public class BundleHost {
return;
}
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
- LinkedList dependentBundles = new LinkedList(
+ LinkedList dependentBundles = new LinkedList<>(
frameworkWiring.getDependencyClosure(Collections.singleton(bundle)));
while (!dependentBundles.isEmpty()) {
Bundle dependentBundle = dependentBundles.pop();
@@ -596,7 +579,8 @@ public class BundleHost {
}
/**
- * Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED" state.
+ * Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED"
+ * state.
*
* @param bundleLocation the bundle location identifier
* @throws InterruptedException if the wait is interrupted
@@ -611,8 +595,8 @@ public class BundleHost {
}
/**
- * Refreshes the specified bundles. This forces the update (replacement)
- * or removal of packages exported by the specified bundles.
+ * Refreshes the specified bundles. This forces the update (replacement) or removal of packages
+ * exported by the specified bundles.
*
* @param bundles the bundles to refresh
* @see FrameworkWiring#refreshBundles
@@ -620,22 +604,19 @@ public class BundleHost {
protected void refreshBundlesSynchronously(Collection bundles) {
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
final CountDownLatch latch = new CountDownLatch(1);
- frameworkWiring.refreshBundles(bundles, new FrameworkListener() {
- @Override
- public void frameworkEvent(FrameworkEvent event) {
- if (event.getType() == FrameworkEvent.ERROR) {
- Bundle bundle = event.getBundle();
- Msg.error(BundleHost.this,
- String.format("OSGi error refreshing bundle: %s", bundle));
- }
- latch.countDown();
+ frameworkWiring.refreshBundles(bundles, event -> {
+ if (event.getType() == FrameworkEvent.ERROR) {
+ Bundle bundle = event.getBundle();
+ Msg.error(BundleHost.this,
+ String.format("OSGi error refreshing bundle: %s", bundle));
}
+ latch.countDown();
});
try {
latch.await();
}
catch (InterruptedException e) {
- e.printStackTrace();
+ Msg.error(this, "Exception waiting for bundles to refresh", e);
}
}
@@ -649,8 +630,11 @@ public class BundleHost {
}
/**
- * Activate a set of bundles and any dependencies in topological order. This method doesn't rely on the
- * framework, and so will add non-active dependencies.
+ * Activate a set of bundles and any dependencies in topological order. This method doesn't
+ * rely on the framework, and so will add non-active dependencies.
+ *
+ * To load bundles without loading inactive dependencies, call
+ * {@link #activateInStages(Collection, TaskMonitor, PrintWriter)}.
*
* @param bundles bundles to activate
* @param monitor a task monitor
@@ -671,9 +655,13 @@ public class BundleHost {
activateSynchronously(bundle.getLocationIdentifier());
}
catch (GhidraBundleException e) {
+ // TODO should we report failing bundles to the console as well so they get logged
+ // in headless mode?
fireBundleException(e);
}
catch (Exception e) {
+ // write the error to the console or log file
+ console.println("Unexpected error activating bundles: " + bundles);
e.printStackTrace(console);
}
monitor.incrementProgress(1);
@@ -682,7 +670,10 @@ public class BundleHost {
/**
* Activate a set of bundles in dependency topological order by resolving against currently
- * active bundles in stages. No bundles outside those requested will be activated.
+ * active bundles in stages. No bundles outside those requested will be activated.
+ *
+ *
To have inactive dependencies loaded, call
+ * {@link #activateAll(Collection, TaskMonitor, PrintWriter)}.
*
* @param bundles bundles to activate
* @param monitor a task monitor
@@ -702,11 +693,13 @@ public class BundleHost {
requirementMap.put(bundle, requirements);
}
catch (GhidraBundleException e) {
+ // TODO should we report failing bundles to the console as well so they get logged
+ // in headless mode?
fireBundleException(e);
}
}
- List bundlesRemaining = new ArrayList<>(requirementMap.keySet());
+ List bundlesRemaining = new ArrayList<>(requirementMap.keySet());
monitor.setMaximum(bundlesRemaining.size());
while (!bundlesRemaining.isEmpty() && !monitor.isCancelled()) {
List resolvableBundles = bundlesRemaining.stream()
@@ -731,9 +724,13 @@ public class BundleHost {
activateSynchronously(bundle.getLocationIdentifier());
}
catch (GhidraBundleException e) {
+ // TODO should we report failing bundles to the console as well so they get logged
+ // in headless mode?
fireBundleException(e);
}
catch (Exception e) {
+ // write the error to the console or log file
+ console.println("Unexpected error activating bundles: " + bundles);
e.printStackTrace(console);
}
monitor.incrementProgress(1);
@@ -813,7 +810,7 @@ public class BundleHost {
*
* Bundles that had been active are reactivated.
*
- *
note: This is done once on startup after system bundles have been added.
+ *
Note: This is done once on startup after system bundles have been added.
*
* @param saveState the state object
* @param tool the tool
@@ -863,7 +860,7 @@ public class BundleHost {
}
if (!bundlesToActivate.isEmpty()) {
- TaskLauncher.launchNonModal("restoring bundle state",
+ TaskLauncher.launchNonModal("Restoring bundle state",
(monitor) -> activateInStages(bundlesToActivate, monitor, new NullPrintWriter()));
}
}
@@ -896,35 +893,43 @@ public class BundleHost {
saveState.putBooleans(SAVE_STATE_TAG_SYSTEM, bundleIsSystem);
}
- private static class Dependency {
- // exists only to be distinguished by id
+//=================================================================================================
+// Inner Classes
+//=================================================================================================
+
+ private static class BundleEdge {
+ // edge type for dependency graph
}
/**
- * Utility class to build a dependency graph from bundles where capabilities map to requirements.
+ * Utility class to build a dependency graph from bundles where capabilities map to
+ * requirements.
*/
- private class BundleDependencyGraph extends DirectedMultigraph {
- final Map> capabilityMap = new HashMap<>();
- final List availableBundles;
- final TaskMonitor monitor;
+ private class BundleDependencyGraph extends DirectedMultigraph {
- BundleDependencyGraph(Collection startingBundles, TaskMonitor monitor) {
+ private final List availableBundles = new ArrayList<>();
+ private final Map> capabilityMap = new HashMap<>();
+ private final TaskMonitor monitor;
+
+ BundleDependencyGraph(Collection activatingBundles, TaskMonitor monitor) {
super(null, null, false);
this.monitor = monitor;
- // maintain a list of bundles available for resolution, starting with all of the enabled bundles
- this.availableBundles = new ArrayList<>();
+ // maintain a list of bundles available for resolution, starting with all of the
+ // enabled bundles
for (GhidraBundle bundle : getGhidraBundles()) {
if (bundle.isEnabled()) {
addToAvailable(bundle);
}
}
+
// An edge A->B indicates that the capabilities of A resolve some requirement(s) of B
- // "front" accumulates bundles and links to bundles already in the graph that they provide capabilities for.
- // e.g. if front[A]=[B,...] then A->B, B is already in the graph, and we will add A next iteration.
+ // "front" accumulates bundles and links to bundles already in the graph that they
+ // provide capabilities for. e.g., if front[A]=[B,...] then A->B, B is already in the
+ // graph, and we will add A next iteration.
Map> front = new HashMap<>();
- for (GhidraBundle bundle : startingBundles) {
+ for (GhidraBundle bundle : activatingBundles) {
front.put(bundle, null);
}
@@ -939,46 +944,45 @@ public class BundleHost {
handleBackEdges(newFront);
front = newFront;
}
-
}
Iterable inTopologicalOrder() {
return () -> new TopologicalOrderIterator<>(this);
}
- void handleBackEdges(Map> newFront) {
- Iterator>> newFrontIter =
+ private void handleBackEdges(Map> newFront) {
+ Iterator>> it =
newFront.entrySet().iterator();
- while (newFrontIter.hasNext() && !monitor.isCancelled()) {
- Entry> entry = newFrontIter.next();
+ while (it.hasNext() && !monitor.isCancelled()) {
+ Entry> entry = it.next();
GhidraBundle source = entry.getKey();
if (containsVertex(source)) {
for (GhidraBundle destination : entry.getValue()) {
if (source != destination) {
- addEdge(source, destination, new Dependency());
+ addEdge(source, destination, new BundleEdge());
}
}
- newFrontIter.remove();
+ it.remove();
}
}
}
- void addFront(Map> front) {
- for (Entry> e : front.entrySet()) {
- GhidraBundle source = e.getKey();
+ private void addFront(Map> front) {
+ for (Entry> entry : front.entrySet()) {
+ GhidraBundle source = entry.getKey();
if (addToAvailable(source)) {
addVertex(source);
- Set destinations = e.getValue();
- if (destinations != null) {
- for (GhidraBundle destination : destinations) {
- addEdge(source, destination, new Dependency());
+ Set dependents = entry.getValue();
+ if (dependents != null) {
+ for (GhidraBundle destination : dependents) {
+ addEdge(source, destination, new BundleEdge());
}
}
}
}
}
- boolean addToAvailable(GhidraBundle bundle) {
+ private boolean addToAvailable(GhidraBundle bundle) {
try {
capabilityMap.put(bundle, bundle.getAllCapabilities());
availableBundles.add(bundle);
@@ -990,33 +994,38 @@ public class BundleHost {
}
}
- // populate newFront with edges depBundle -> bundle,
- // where depBundle has a capability that resolves a requirement of bundle
- void resolve(GhidraBundle bundle, Map> newFront) {
+ // Populate newFront with edges supplierBundle -> dependentBundle, where supplierBundle has
+ // a capability that resolves a requirement of dependentBundle. Items added to newFront are
+ // already in the graph. These items will be added if the given bundle to resolve
+ // becomes a dependent on this added supplier.
+ private void resolve(GhidraBundle toResolve,
+ Map> newFront) {
List requirements;
try {
- requirements = new ArrayList<>(bundle.getAllRequirements());
- if (requirements.isEmpty()) {
- return;
+ List bundleRequirements = toResolve.getAllRequirements();
+ if (bundleRequirements.isEmpty()) {
+ return; // no dependencies to resolve
}
+ requirements = new ArrayList<>(bundleRequirements);
}
catch (GhidraBundleException e) {
fireBundleException(e);
- removeVertex(bundle);
+ removeVertex(toResolve);
return;
}
- for (GhidraBundle depBundle : availableBundles) {
- for (BundleCapability capability : capabilityMap.get(depBundle)) {
+ for (GhidraBundle supplierBundle : availableBundles) {
+ for (BundleCapability capability : capabilityMap.get(supplierBundle)) {
if (monitor.isCancelled()) {
return;
}
- Iterator reqIter = requirements.iterator();
- while (reqIter.hasNext()) {
- BundleRequirement req = reqIter.next();
+ Iterator it = requirements.iterator();
+ while (it.hasNext()) {
+ BundleRequirement req = it.next();
if (req.matches(capability)) {
- newFront.computeIfAbsent(depBundle, b -> new HashSet<>()).add(bundle);
- reqIter.remove();
+ newFront.computeIfAbsent(supplierBundle, b -> new HashSet<>())
+ .add(toResolve);
+ it.remove();
}
}
if (requirements.isEmpty()) {
@@ -1024,13 +1033,15 @@ public class BundleHost {
}
}
}
- // if requirements remain, some will be resolved by system
- // and others will generate helpful errors for the user during activation
+
+ // If requirements remain, some will be resolved by system and others will generate
+ // helpful errors for the user during activation
}
}
/**
- * The {@code BundleListener} that notifies {@link BundleHostListener}s of bundle activation changes
+ * The {@code BundleListener} that notifies {@link BundleHostListener}s of bundle activation
+ * changes.
*/
private class MyBundleListener implements BundleListener {
private final Bundle systemBundle;
@@ -1050,8 +1061,10 @@ public class BundleHost {
if (STDERR_DEBUGGING) {
String symbolicName = osgiBundle.getSymbolicName();
String locationIdentifier = osgiBundle.getLocation();
- System.err.printf("%s %s from %s\n", OSGiUtils.getEventTypeString(event),
- symbolicName, locationIdentifier);
+ String message =
+ String.format("CC: %s %s from %s\n", OSGiUtils.getEventTypeString(event),
+ symbolicName, locationIdentifier);
+ Msg.debug(this, message);
}
GhidraBundle bundle;
switch (event.getType()) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java
index cee4bf6519..283f9b4f87 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java
@@ -45,7 +45,7 @@ public class BundleMap {
bundlesByLocation.put(bundle.getLocationIdentifier(), bundle);
}
finally {
- lock.writeLock().unlock();
+ writeLock.unlock();
}
}
@@ -197,6 +197,7 @@ public class BundleMap {
}
/**
+ * Returns the currently mapped bundles.
* @return the currently mapped bundles
*/
public Collection getGhidraBundles() {
@@ -210,6 +211,7 @@ public class BundleMap {
}
/**
+ * Returns the currently mapped bundle files.
* @return the currently mapped bundle files
*/
public Collection getBundleFiles() {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
index 5f4239ac34..0933ec6814 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
@@ -45,7 +45,7 @@ import resources.Icons;
import resources.ResourceManager;
/**
- * component for managing OSGi bundle status
+ * Component for managing OSGi bundle status
*/
public class BundleStatusComponentProvider extends ComponentProviderAdapter {
@@ -289,7 +289,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList());
Collection bundles = bundleHost.add(resourceFiles, true, false);
- TaskLauncher.launchNonModal("activating new bundles", (monitor) -> {
+ TaskLauncher.launchNonModal("Activating new bundles", (monitor) -> {
bundleHost.activateAll(bundles, monitor,
getTool().getService(ConsoleService.class).getStdErr());
});
@@ -339,9 +339,6 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
return panel;
}
- /**
- * cleanup this component
- */
public void dispose() {
filterPanel.dispose();
}
@@ -351,9 +348,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
/**
- * This is for testing only! during normal execution, statuses are only added through BundleHostListener bundle(s) added events.
+ * This is for testing only! during normal execution, statuses are only added through
+ * BundleHostListener bundle(s) added events.
*
- * each new bundle will be enabled and writable
+ *
Each new bundle will be enabled and writable
*
* @param bundleFiles the files to use
*/
@@ -363,6 +361,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
.collect(Collectors.toList()));
}
+//=================================================================================================
+// Inner Classes
+//=================================================================================================
+
private final class RemoveBundlesTask extends Task {
private final DeactivateAndDisableBundlesTask deactivateBundlesTask;
private final List statuses;
@@ -378,7 +380,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
public void run(TaskMonitor monitor) throws CancelledException {
deactivateBundlesTask.run(monitor);
monitor.checkCanceled();
- // partition bundles into system (bundles.get(true)) and non-system (bundles.get(false)).
+ // partition bundles into system (bundles.get(true)) / non-system (bundles.get(false))
Map> bundles = statuses.stream()
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getFile()))
.collect(Collectors.partitioningBy(GhidraBundle::isSystemBundle));
@@ -483,8 +485,8 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
}
/*
- * Activating/deactivating a single bundle doesn't require resolving dependents,
- * so this task is slightly different from the others.
+ * Activating/deactivating a single bundle doesn't require resolving dependents, so this task
+ * is slightly different from the others.
*/
private class ActivateDeactivateBundleTask extends Task {
private final BundleStatus status;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
index 7f6221992c..7f7a957baa 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
@@ -22,7 +22,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
-import javax.swing.*;
+import javax.swing.JLabel;
import javax.swing.event.TableModelEvent;
import org.osgi.framework.Bundle;
@@ -32,8 +32,10 @@ import generic.jar.ResourceFile;
import generic.util.Path;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
-import ghidra.util.*;
-import ghidra.util.table.column.*;
+import ghidra.util.Swing;
+import ghidra.util.SystemUtilities;
+import ghidra.util.table.column.AbstractGColumnRenderer;
+import ghidra.util.table.column.GColumnRenderer;
/**
* Model for {@link BundleStatus} objects.
@@ -73,10 +75,10 @@ public class BundleStatusTableModel
}
private BundleStatus getStatus(GhidraBundle bundle) {
- return getStatusFromLoc(bundle.getLocationIdentifier());
+ return getStatusFromLocation(bundle.getLocationIdentifier());
}
- private BundleStatus getStatusFromLoc(String bundleLoc) {
+ private BundleStatus getStatusFromLocation(String bundleLoc) {
return bundleLocToStatusMap.get(bundleLoc);
}
@@ -254,7 +256,7 @@ public class BundleStatusTableModel
// wrap the assigned comparator to detect if the order changes
AtomicBoolean changed = new AtomicBoolean(false);
- Comparator wrapper = new Comparator() {
+ Comparator wrapper = new Comparator<>() {
Comparator comparator = sortingContext.getComparator();
@Override
@@ -367,7 +369,7 @@ public class BundleStatusTableModel
@Override
public void bundleException(GhidraBundleException exception) {
Swing.runLater(() -> {
- BundleStatus status = getStatusFromLoc(exception.getBundleLocation());
+ BundleStatus status = getStatusFromLocation(exception.getBundleLocation());
if (status != null) {
status.setSummary(exception.getMessage());
int rowIndex = getRowIndex(status);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java
index 5043ae9c03..492ec60b9a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java
@@ -30,8 +30,20 @@ import generic.jar.ResourceFile;
*/
public abstract class GhidraBundle {
- protected final ResourceFile file;
+ /**
+ * A {@link GhidraBundle} can be
+ *
+ * - a Bndtools .bnd script
+ * - an OSGi bundle .jar file
+ * - a directory of Java source
+ *
+ *
+ */
+ enum Type {
+ BND_SCRIPT, JAR, SOURCE_DIR, INVALID
+ }
+ protected final ResourceFile bundleFile; // can be a dir or a jar file
protected final BundleHost bundleHost;
protected boolean enabled;
protected boolean systemBundle;
@@ -39,29 +51,29 @@ public abstract class GhidraBundle {
GhidraBundle(BundleHost bundleHost, ResourceFile bundleFile, boolean enabled,
boolean systemBundle) {
this.bundleHost = bundleHost;
- this.file = bundleFile;
+ this.bundleFile = bundleFile;
this.enabled = enabled;
this.systemBundle = systemBundle;
}
/**
- * clean build artifacts generated during build of this bundle
+ * Clean build artifacts generated during build of this bundle.
*
* @return true if anything was done
*/
abstract boolean clean();
/**
- * build OSGi bundle if possible
+ * Build OSGi bundle if needed and if possible.
*
* @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 or could not build
* @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}.
+ * Same as {@link #build(PrintWriter)} with writer = {@link System#err}.
*
* @return true if build happened, false if already built
* @throws Exception if the build cannot complete
@@ -74,28 +86,43 @@ public abstract class GhidraBundle {
* Return the location identifier of the bundle that this GhidraBundle represents.
*
* The location identifier is used by the framework, e.g. it is passed to
- * {@link org.osgi.framework.BundleContext#installBundle} when the bundle is
- * first installed.
+ * {@link org.osgi.framework.BundleContext#installBundle} when the bundle is first installed.
*
- *
Although the bundle location is a URI, outside of interactions with the framework,
- * the bundle location should remain opaque.
+ *
Although the bundle location is a URI, outside of interactions with the framework, the
+ * bundle location should remain opaque.
*
- * @return location identifier of this bundle
+ * @return location identifier of this bundle
*/
public abstract String getLocationIdentifier();
+ /**
+ * Returns all bundle requirements.
+ *
+ * @return the requirements
+ * @throws GhidraBundleException if there is an exception parsing / loading bundle requirements
+ */
public abstract List getAllRequirements() throws GhidraBundleException;
+ /**
+ * Returns all bundle capabilities.
+ *
+ * @return the capabilities
+ * @throws GhidraBundleException if there is an exception parsing / loading bundle capabilities
+ */
public abstract List getAllCapabilities() throws GhidraBundleException;
/**
- * @return the file where this bundle is loaded from
+ * The file where this bundle is loaded from.
+ *
+ * @return the file from where this bundle is loaded
*/
public ResourceFile getFile() {
- return file;
+ return bundleFile;
}
/**
+ * True if this bundle is enabled.
+ *
* @return true if this bundle is enabled
*/
public boolean isEnabled() {
@@ -103,7 +130,7 @@ public abstract class GhidraBundle {
}
/**
- * set the enablement flag for this bundle.
+ * Set the enablement flag for this bundle.
*
* If a bundle is enabled its contents will be scanned, e.g. for scripts.
*
@@ -123,7 +150,7 @@ public abstract class GhidraBundle {
}
/**
- * Get the type of a GhidraBundle from its file.
+ * Get the type of {@link GhidraBundle} from its file.
*
* @param file a bundle file
* @return the type
@@ -163,8 +190,8 @@ public abstract class GhidraBundle {
}
/**
- * Get the OSGi bundle represented by this GhidraBundle or null if it isn't in
- * the "installed" state.
+ * Get the OSGi bundle represented by this GhidraBundle or null if it isn't in the "installed"
+ * state.
*
* @return a Bundle or null
*/
@@ -173,6 +200,8 @@ public abstract class GhidraBundle {
}
/**
+ * True if this bundle is active.
+ *
* @return true if this bundle is active
*/
public boolean isActive() {
@@ -180,17 +209,8 @@ public abstract class GhidraBundle {
return (bundle != null) && bundle.getState() == Bundle.ACTIVE;
}
- /**
- * A GhidraBundle can be
- *
- * - a Bndtools .bnd script
- * - an OSGi bundle .jar file
- * - a directory of Java source
- *
- *
- */
- enum Type {
- BND_SCRIPT, JAR, SOURCE_DIR, INVALID
+ @Override
+ public String toString() {
+ return getOSGiBundle().getSymbolicName();
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraJarBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraJarBundle.java
index cfe4965002..1d26c6f9b7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraJarBundle.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraJarBundle.java
@@ -67,7 +67,7 @@ public class GhidraJarBundle extends GhidraBundle {
}
protected ManifestParser createManifestParser() throws GhidraBundleException {
- try (Jar jar = new Jar(file.getFile(true))) {
+ try (Jar jar = new Jar(bundleFile.getFile(true))) {
Manifest manifest = jar.getManifest();
if (manifest == null) {
throw new GhidraBundleException(bundleLocation, "jar bundle with no manifest");
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java
index 68f8c51ac7..e108d13817 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java
@@ -35,8 +35,7 @@ import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
+import org.osgi.framework.*;
import org.osgi.framework.Constants;
import org.osgi.framework.wiring.*;
import org.phidias.compile.BundleJavaManager;
@@ -47,17 +46,16 @@ import generic.io.NullPrintWriter;
import generic.jar.ResourceFile;
import ghidra.app.script.*;
import ghidra.util.Msg;
+import util.CollectionUtils;
import utilities.util.FileUtilities;
/**
- * {@link GhidraSourceBundle} represents a Java source directory that is compiled on build to an OSGi bundle.
+ * Represents a Java source directory that is compiled on build to an OSGi bundle.
*
* A manifest and {@link BundleActivator} are generated if not already present.
*/
-/**
- *
- */
public class GhidraSourceBundle extends GhidraBundle {
+ private static final String INSTRCTION_ACTIVATOR = "org.osgi.framework.BundleActivator";
private static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
private static final String GENERATED_VERSION = "1.0";
@@ -70,43 +68,53 @@ public class GhidraSourceBundle extends GhidraBundle {
private static final Predicate IS_CLASS_FILE =
Pattern.compile("(\\$.*)?\\.class", Pattern.CASE_INSENSITIVE).asMatchPredicate();
- protected interface DiscrepencyCallback {
+ /**
+ * Used to report source and class file deviation
+ */
+ protected interface DiscrepancyCallback {
/**
- * Invoked when there is a discrepancy between {@code sourceFile} and its
- * corresponding class file(s), {@code classFiles}
+ * Invoked when there is a discrepancy between {@code sourceFile} and its corresponding
+ * class file(s), {@code classFiles}
*
- * @param sourceFile the source file or null to indicate the class files have no corresponding source
+ * @param sourceFile the source file or null to indicate the class files have no
+ * corresponding source
* @param classFiles corresponding class file(s)
* @throws Throwable an exception
*/
void found(ResourceFile sourceFile, Collection classFiles) throws Throwable;
}
+ // This is a hash; it is used to create a unique directory name for this bundle
private final String symbolicName;
private final Path binaryDir;
- private final String bundleLoc;
+ /**
+ * The bundle location id
+ * @see #getLocationIdentifier()
+ */
+ private final String bundleLocationId;
+
+ // These 2 lists are updated to track source file to class file changes; newSources are those
+ // that need to be compiled; oldBinaries are those that no longer have source files (such as
+ // when a source file is deleted)
private final List newSources = new ArrayList<>();
private final List oldBinaries = new ArrayList<>();
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- //// information indexed by source file
+ // information indexed by source file
private final Map buildErrors = new HashMap<>();
private final Map> sourceFileToRequirements =
new HashMap<>();
private final Map> requirementToSourceFileMap = new HashMap<>();
+ private final Set missedRequirements = new HashSet<>();
private final Set importPackageValues = new HashSet<>();
- private Set missedRequirements = new HashSet<>();
-
- private long lastCompileAttempt;
-
/**
* Create a new GhidraSourceBundle.
*
- * @param bundleHost the {@link BundleHost} instance this bundle will belong to
+ * @param bundleHost the instance this bundle will belong to
* @param sourceDirectory the source bundle directory
* @param enabled true to start enabled
* @param systemBundle true if this is a Ghidra system bundle
@@ -115,18 +123,18 @@ public class GhidraSourceBundle extends GhidraBundle {
boolean systemBundle) {
super(bundleHost, sourceDirectory, enabled, systemBundle);
- this.symbolicName = GhidraSourceBundle.sourceDirHash(getSourceDirectory());
- this.binaryDir = GhidraSourceBundle.getCompiledBundlesDir().resolve(symbolicName);
-
- this.bundleLoc = "reference:file://" + binaryDir.toAbsolutePath().normalize().toString();
+ this.symbolicName = sourceDirHash(getSourceDirectory());
+ this.binaryDir = getCompiledBundlesDir().resolve(symbolicName);
+ this.bundleLocationId =
+ "reference:file://" + binaryDir.toAbsolutePath().normalize().toString();
}
/**
* (alias of {@link #getFile} for readability)
* @return the source directory this bundle represents
*/
- protected final ResourceFile getSourceDirectory() {
- return file;
+ private final ResourceFile getSourceDirectory() {
+ return bundleFile;
}
/**
@@ -142,8 +150,8 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * When a source bundle doesn't have a manifest, Ghidra computes the bundle's
- * symbolic name as a hash of the source directory path.
+ * When a source bundle doesn't have a manifest, Ghidra computes the bundle's symbolic name as
+ * a hash of the source directory path.
*
* This hash is also used as the final path component of the compile destination:
*
{@code $USERHOME/.ghidra/.ghidra_/osgi/compiled-bundles/ }
@@ -157,18 +165,6 @@ public class GhidraSourceBundle extends GhidraBundle {
return Integer.toHexString(sourceDir.getAbsolutePath().hashCode());
}
- /**
- * for testing only!!!
- *
- * @param sourceFile a ghidra script file
- * @return the directory its class is compiled to
- */
- public static Path getBindirFromScriptFile(ResourceFile sourceFile) {
- ResourceFile tmpSourceDir = sourceFile.getParentFile();
- String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
- return GhidraSourceBundle.getCompiledBundlesDir().resolve(tmpSymbolicName);
- }
-
/**
* Return the class name corresponding to a script in this source bundle.
*
@@ -188,16 +184,16 @@ public class GhidraSourceBundle extends GhidraBundle {
return relativePath.replace(File.separatorChar, '.');
}
- void clearBuildErrors(ResourceFile sourceFile) {
+ private void clearBuildErrors(ResourceFile sourceFile) {
buildErrors.remove(sourceFile);
}
/**
- * append build error
+ * Append the given build error.
* @param sourceFile the file w/ errors
* @param err an error string
*/
- protected void buildError(ResourceFile sourceFile, String err) {
+ private void buildError(ResourceFile sourceFile, String err) {
BuildError error = buildErrors.computeIfAbsent(sourceFile, BuildError::new);
error.append(err);
}
@@ -206,7 +202,7 @@ public class GhidraSourceBundle extends GhidraBundle {
* Get any errors associated with building the given source file.
*
* @param sourceFile the source file
- * @return a {@link BuildError} object
+ * @return the build error or null if no errors
*/
public BuildError getErrors(ResourceFile sourceFile) {
return buildErrors.get(sourceFile);
@@ -233,9 +229,10 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * update buildReqs based on \@importpackage tag in java files in the default(unnamed) package
+ * Update build requirements based on {@code @importpackage} tag in java files in the
+ * default (unnamed) package.
*
- * @throws GhidraBundleException on failure to parse the \@importpackage tag
+ * @throws GhidraBundleException on failure to parse the {@code @importpackage} tag
*/
private void updateRequirementsFromMetadata() throws GhidraBundleException {
sourceFileToRequirements.clear();
@@ -243,38 +240,42 @@ public class GhidraSourceBundle extends GhidraBundle {
importPackageValues.clear();
for (ResourceFile rootSourceFile : getSourceDirectory().listFiles()) {
- if (rootSourceFile.getName().endsWith(".java")) {
- // without GhidraScriptComponentProvider.updateAvailableScriptFilesForDirectory,
- // or GhidraScriptComponentProvider.newScript this might be the earliest need for
- // ScriptInfo, so allow construction.
+ if (!rootSourceFile.getName().endsWith(".java")) {
+ continue;
+ }
- // NB: ScriptInfo will update field values if lastModified has changed since last time they were computed
- String importPackage = parseImportPackageMetadata(rootSourceFile);
- if (importPackage != null && !importPackage.isEmpty()) {
- importPackageValues.addAll(
- ManifestParser.parseDelimitedString(importPackage.strip(), ","));
+ // Without GhidraScriptComponentProvider.updateAvailableScriptFilesForDirectory, or
+ // GhidraScriptComponentProvider.newScript this might be the earliest need for
+ // ScriptInfo, so allow construction.
- List requirements;
- try {
- requirements = OSGiUtils.parseImportPackage(importPackage);
- }
- catch (BundleException e) {
- throw new GhidraBundleException(getLocationIdentifier(),
- "@importpackage error", e);
- }
- sourceFileToRequirements.put(rootSourceFile, requirements);
- for (BundleRequirement requirement : requirements) {
- requirementToSourceFileMap
- .computeIfAbsent(requirement.toString(), x -> new ArrayList<>())
- .add(rootSourceFile);
- }
- }
+ // NB: ScriptInfo will update field values if lastModified has changed since last time
+ // they were computed
+ String importPackage = parseImportPackageMetadata(rootSourceFile);
+ if (importPackage == null || importPackage.isEmpty()) {
+ continue;
+ }
+
+ List parts = ManifestParser.parseDelimitedString(importPackage.strip(), ",");
+ importPackageValues.addAll(parts);
+ List requirements;
+ try {
+ requirements = OSGiUtils.parseImportPackage(importPackage);
+ }
+ catch (BundleException e) {
+ throw new GhidraBundleException(getLocationIdentifier(),
+ "@importpackage error", e);
+ }
+ sourceFileToRequirements.put(rootSourceFile, requirements);
+ for (BundleRequirement requirement : requirements) {
+ requirementToSourceFileMap
+ .computeIfAbsent(requirement.toString(), x -> new ArrayList<>())
+ .add(rootSourceFile);
}
}
}
/**
- * assumes that {@link #updateRequirementsFromMetadata()} has been called recently
+ * Assumes that {@link #updateRequirementsFromMetadata()} has been called recently
*
* @return deduped requirements
*/
@@ -288,30 +289,31 @@ public class GhidraSourceBundle extends GhidraBundle {
return dedupedReqs;
}
- protected ManifestParser createSourceManifestParser() {
+ private ManifestParser createSourceManifestParser() {
ResourceFile manifestFile = getSourceManifestFile();
- if (manifestFile.exists()) {
- try (InputStream manifestInputStream = manifestFile.getInputStream()) {
- Manifest manifest = new Manifest(manifestInputStream);
- Attributes mainAttributes = manifest.getMainAttributes();
- Map headerMap = mainAttributes.entrySet()
- .stream()
- .collect(Collectors.toMap(e -> e.getKey().toString(),
- e -> e.getValue().toString()));
- return new ManifestParser(null, null, null, headerMap);
- }
- catch (IOException | BundleException e) {
- throw new RuntimeException(e);
- }
+ if (!manifestFile.exists()) {
+ return null;
+ }
+
+ try (InputStream is = manifestFile.getInputStream()) {
+ Manifest manifest = new Manifest(is);
+ Attributes mainAttributes = manifest.getMainAttributes();
+ Map headerMap = mainAttributes.entrySet()
+ .stream()
+ .collect(Collectors.toMap(e -> e.getKey().toString(),
+ e -> e.getValue().toString()));
+ return new ManifestParser(null, null, null, headerMap);
+ }
+ catch (IOException | BundleException e) {
+ throw new RuntimeException(e);
}
- return null;
}
@Override
public List getAllRequirements() throws GhidraBundleException {
- ManifestParser manifestParser = createSourceManifestParser();
- if (manifestParser != null) {
- return manifestParser.getRequirements();
+ ManifestParser parser = createSourceManifestParser();
+ if (parser != null) {
+ return parser.getRequirements();
}
updateRequirementsFromMetadata();
@@ -319,17 +321,17 @@ public class GhidraSourceBundle extends GhidraBundle {
return new ArrayList<>(reqs.values());
}
- protected static void findPackageDirs(List packages, ResourceFile directory) {
- for (ResourceFile file : directory
- .listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"))) {
+ private static void findPackageDirs(List packages, ResourceFile dir) {
+ boolean added = false;
+ ResourceFile[] files = dir.listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"));
+ for (ResourceFile file : files) {
if (!file.getName().matches("internal|private")) {
- boolean added = false;
if (file.isDirectory()) {
findPackageDirs(packages, file);
}
else if (!added) {
added = true;
- packages.add(directory.getAbsolutePath());
+ packages.add(dir.getAbsolutePath());
}
}
}
@@ -363,21 +365,23 @@ public class GhidraSourceBundle extends GhidraBundle {
return OSGiUtils.parseExportPackage(sb.substring(1));
}
catch (BundleException e) {
- throw new GhidraBundleException(getLocationIdentifier(), "exports error", e);
+ throw new GhidraBundleException(getLocationIdentifier(), "Exports error", e);
}
}
/**
- * look for new sources, metadata, manifest file.
+ * Look for new sources, metadata, manifest file. This will find files that need to be
+ * compiled and files that need to be removed.
*
- * if files had errors last time, haven't changed, and no new requirements are available, remove them.
+ *
If files had errors last time, haven't changed, and no new requirements are available,
+ * remove them.
*
* @param writer for reporting status to user
* @throws IOException while accessing manifest file
* @throws OSGiException while parsing imports
*/
- void updateFromFilesystem(PrintWriter writer) throws IOException, OSGiException {
- // look for new source files
+ private void updateFromFilesystem(PrintWriter writer) throws IOException, OSGiException {
+
newSources.clear();
oldBinaries.clear();
@@ -395,13 +399,13 @@ public class GhidraSourceBundle extends GhidraBundle {
}
});
- // we don't want to rebuild source files that had errors last time and haven't changed,
- // so remove them from newSources. Also remove old error messages.
- Iterator newSourceIterator = newSources.iterator();
- while (newSourceIterator.hasNext()) {
- ResourceFile newSourceFile = newSourceIterator.next();
+ // we don't want to rebuild source files that had errors last time and haven't changed, so
+ // remove them from newSources. Also remove old error messages.
+ Iterator it = newSources.iterator();
+ while (it.hasNext()) {
+ ResourceFile newSourceFile = it.next();
if (stillHasErrors(newSourceFile)) {
- newSourceIterator.remove();
+ it.remove();
}
else {
// any errors are old, so remove them
@@ -421,46 +425,40 @@ public class GhidraSourceBundle extends GhidraBundle {
}
private void deleteOldBinaries() throws IOException {
- // dedupe and omit files that don't exist
+ // dedup and omit files that don't exist
oldBinaries.sort(null);
- Iterable paths =
- () -> oldBinaries.stream().distinct().filter(Files::exists).iterator();
- for (Path path : paths) {
+ Iterator toDelete =
+ oldBinaries.stream().distinct().filter(Files::exists).sorted().iterator();
+ for (Path path : CollectionUtils.asIterable(toDelete)) {
Files.delete(path);
}
+
+ oldBinaries.clear();
}
- int getBuildErrorCount() {
+ private int getBuildErrorCount() {
return buildErrors.size();
}
- int getNewSourcesCount() {
+ private int getNewSourcesCount() {
return newSources.size();
}
/**
- * used just after {@link #build} to get the newly compiled source files
+ * Used just after {@link #build} to get the newly compiled source files.
* @return new source files
*/
public List getNewSources() {
- return newSources;
- }
-
- void compileAttempted() {
- lastCompileAttempt = System.currentTimeMillis();
- }
-
- long getLastCompileAttempt() {
- return lastCompileAttempt;
+ return Collections.unmodifiableList(newSources);
}
@Override
public String getLocationIdentifier() {
- return bundleLoc;
+ return bundleLocationId;
}
- ResourceFile getSourceManifestFile() {
+ private ResourceFile getSourceManifestFile() {
return new ResourceFile(getSourceDirectory(), "META-INF" + File.separator + "MANIFEST.MF");
}
@@ -468,19 +466,20 @@ public class GhidraSourceBundle extends GhidraBundle {
return binaryDir.resolve("META-INF").resolve("MANIFEST.MF");
}
- boolean hasSourceManifest() {
+ private boolean hasSourceManifest() {
return getSourceManifestFile().exists();
}
- boolean hasNewManifest() {
+ private boolean hasNewManifest() {
ResourceFile sourceManifest = getSourceManifestFile();
Path binaryManifest = getBinaryManifestPath();
- return sourceManifest.exists() && (Files.notExists(binaryManifest) ||
- sourceManifest.lastModified() > binaryManifest.toFile().lastModified());
+ boolean oldOrMissingBinaryManifest = Files.notExists(binaryManifest) ||
+ sourceManifest.lastModified() > binaryManifest.toFile().lastModified();
+ return sourceManifest.exists() && oldOrMissingBinaryManifest;
}
- protected static boolean wipeContents(Path path) throws IOException {
+ private static boolean wipeContents(Path path) throws IOException {
if (Files.exists(path)) {
boolean anythingDeleted = false;
try (Stream walk = Files.walk(path)) {
@@ -498,12 +497,12 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * if source with a previous requirement error now resolves, add it to newSources.
+ * If source with a previous requirement error now resolves, add it to newSources.
*
- * The reason for the previous build error isn't necessarily a missing requirement,
- * but this shouldn't be too expensive.
+ *
The reason for the previous build error isn't necessarily a missing requirement, but this
+ * shouldn't be too expensive.
*/
- private void addSourcesIfResolutionWillPass() {
+ private void addSourcesThatNoLongerHaveMissingRequirements() {
for (ResourceFile sourceFile : buildErrors.keySet()) {
List requirements = sourceFileToRequirements.get(sourceFile);
if (requirements != null && !requirements.isEmpty() &&
@@ -519,10 +518,10 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * if a file that previously built without errors is now missing some requirements,
- * rebuild it to capture errors (if any).
+ * If a file that previously built without errors is now missing some requirements, rebuild it
+ * to capture errors (if any).
*/
- void addSourcesIfResolutionWillFail() {
+ private void addSourcesThatNowHaveMissingRequirements() {
// if previous successes no longer resolve, (cleanup) and try again
for (Entry> e : sourceFileToRequirements.entrySet()) {
ResourceFile sourceFile = e.getKey();
@@ -542,39 +541,42 @@ public class GhidraSourceBundle extends GhidraBundle {
@Override
public boolean build(PrintWriter writer) throws Exception {
- if (writer == null) {
- writer = new NullPrintWriter();
- }
+ writer = NullPrintWriter.dummyIfNull(writer);
boolean needsCompile = false;
if (hasSourceManifest()) {
sourceFileToRequirements.clear();
requirementToSourceFileMap.clear();
- ArrayList reqs = new ArrayList<>(getAllRequirements());
+ List reqs = new ArrayList<>(getAllRequirements());
bundleHost.resolve(reqs);
- HashSet newMissedRequirements = new HashSet<>();
+ Set newMissedRequirements = new HashSet<>();
for (BundleRequirement req : reqs) {
newMissedRequirements.add(req.toString());
}
if (hasNewManifest() || !newMissedRequirements.equals(missedRequirements)) {
- missedRequirements = newMissedRequirements;
+ missedRequirements.clear();
+ missedRequirements.addAll(newMissedRequirements);
wipeBinDir();
buildErrors.clear();
}
updateFromFilesystem(writer);
}
else {
- updateFromFilesystem(writer);
+ // this gets all source files from the file system that we know need to be compiled
+ // based upon missing or outdated class files
+ updateFromFilesystem(writer);
updateRequirementsFromMetadata();
- addSourcesIfResolutionWillPass();
- addSourcesIfResolutionWillFail();
+
+ // these 2 calls handle source files that need to be compiled based upon changes in
+ // added or removed bundles / requirements
+ addSourcesThatNoLongerHaveMissingRequirements();
+ addSourcesThatNowHaveMissingRequirements();
}
int buildErrorsLastTime = getBuildErrorCount();
int newSourceCount = getNewSourcesCount();
-
if (newSourceCount == 0) {
if (buildErrorsLastTime > 0) {
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
@@ -600,7 +602,7 @@ public class GhidraSourceBundle extends GhidraBundle {
bundleHost.deactivateSynchronously(osgiBundle);
}
- // once we've committed to recompile and regenerate generated classes, delete the old stuff
+ // once we've committed to recompile and regenerate classes, delete the old stuff
deleteOldBinaries();
String summary = compileToExplodedBundle(writer);
@@ -635,16 +637,19 @@ public class GhidraSourceBundle extends GhidraBundle {
private ResourceFile[] correspondingBinaries(ResourceFile source) {
String parentPath = source.getParentFile().getAbsolutePath();
- String relPath = parentPath.substring(getSourceDirectory().getAbsolutePath().length());
- if (relPath.startsWith(File.separator)) {
- relPath = relPath.substring(1);
+ int sourceDirLength = getSourceDirectory().getAbsolutePath().length();
+ String relativePath = parentPath.substring(sourceDirLength);
+ if (relativePath.startsWith(File.separator)) {
+ relativePath = relativePath.substring(1);
}
- String className0 = source.getName();
- String className = className0.substring(0, className0.length() - 5);// drop ".java"
- ResourceFile binarySubdir = new ResourceFile(binaryDir.resolve(relPath).toFile());
+
+ String javaFileName = source.getName();
+ String className = javaFileName.substring(0, javaFileName.length() - 5); // drop ".java"
+ ResourceFile binarySubdir = new ResourceFile(binaryDir.resolve(relativePath).toFile());
if (!binarySubdir.exists() || !binarySubdir.isDirectory()) {
return new ResourceFile[] {};
}
+
return binarySubdir.listFiles(f -> {
String fileName = f.getName();
return fileName.startsWith(className) &&
@@ -674,23 +679,23 @@ public class GhidraSourceBundle extends GhidraBundle {
* binary_root/com/blah/Blah$Inner$Innerer.class
* ...
*
- * @param callback callback
+ * @param discrepancy the discrepancy callback
*/
- protected void visitDiscrepancies(DiscrepencyCallback callback) {
+ protected void visitDiscrepancies(DiscrepancyCallback discrepancy) {
try {
- Deque stack = new ArrayDeque<>();
- // start in the source directory root
- stack.add(getSourceDirectory());
+ Deque stack = new ArrayDeque<>();
+ ResourceFile sourceDir = getSourceDirectory();
+ stack.add(sourceDir); // start in the source directory root
while (!stack.isEmpty()) {
ResourceFile sourceSubdir = stack.pop();
String relPath = sourceSubdir.getAbsolutePath()
- .substring(getSourceDirectory().getAbsolutePath().length());
+ .substring(sourceDir.getAbsolutePath().length());
if (relPath.startsWith(File.separator)) {
relPath = relPath.substring(1);
}
- Path binarySubdir = binaryDir.resolve(relPath);
+ Path binarySubdir = binaryDir.resolve(relPath);
ClassMapper mapper = new ClassMapper(binarySubdir);
// for each source file, lookup class files by class name
@@ -701,13 +706,13 @@ public class GhidraSourceBundle extends GhidraBundle {
else {
List classFiles = mapper.findAndRemove(sourceFile);
if (classFiles != null) {
- callback.found(sourceFile, classFiles);
+ discrepancy.found(sourceFile, classFiles);
}
}
}
// any remaining .class files are missing .java files
if (mapper.hasExtraClassFiles()) {
- callback.found(null, mapper.extraClassFiles());
+ discrepancy.found(null, mapper.extraClassFiles());
}
}
}
@@ -716,12 +721,15 @@ public class GhidraSourceBundle extends GhidraBundle {
}
}
- /* requirements that resolve internally are never "missing", but will only resolve _after_ build/install */
+ /*
+ * Requirements that resolve internally are never "missing", but resolve _after_ build/install
+ */
private boolean resolveInternally(List requirements)
throws GhidraBundleException {
if (requirements.isEmpty()) {
return true;
}
+
List capabilities = getAllCapabilities();
Iterator requirementIterator = requirements.iterator();
boolean anyMissing = false;
@@ -738,73 +746,79 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/*
- * when calling the java compiler programmatically, we map import requests to files with
- * a custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that
- * handles ResourceFiles then wrap that with Phidia, which handles imports based on
- * bundle requirements.
+ * When calling the java compiler programmatically, we map import requests to files with a
+ * custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that handles
+ * ResourceFiles then wrap that with phidias, which handles imports based on bundle
+ * requirements.
*/
private BundleJavaManager createBundleJavaManager(PrintWriter writer, Summary summary,
List options) throws IOException, GhidraBundleException {
- final ResourceFileJavaFileManager resourceFileJavaManager = new ResourceFileJavaFileManager(
- Collections.singletonList(getSourceDirectory()), buildErrors.keySet());
+ ResourceFileJavaFileManager resourceFileJavaManager = new ResourceFileJavaFileManager(
+ Collections.singletonList(getSourceDirectory()), buildErrors.keySet());
BundleJavaManager bundleJavaManager = new MyBundleJavaManager(bundleHost.getHostFramework(),
resourceFileJavaManager, options);
// The phidias BundleJavaManager is for compiling from within a bundle -- it makes the
- // bundle dependencies available to the compiler classpath. Here, we are compiling in an as-yet
- // non-existing bundle, so we forge the wiring based on @importpackage metadata.
+ // bundle dependencies available to the compiler classpath. Here, we are compiling in an
+ // as-yet non-existing bundle, so we forge the wiring based on @importpackage metadata.
// get wires for currently active bundles to satisfy all requirements
List requirements = getAllRequirements();
List bundleWirings = bundleHost.resolve(requirements);
-
if (!resolveInternally(requirements)) {
- writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
- requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
- for (BundleRequirement requirement : requirements) {
- List requiringFiles =
- requirementToSourceFileMap.get(requirement.toString());
- if (requiringFiles != null && requiringFiles.size() > 0) {
- writer.printf(" %s, from %s\n", requirement.toString(),
- requiringFiles.stream()
- .map(generic.util.Path::toPathString)
- .collect(Collectors.joining(",")));
- for (ResourceFile sourceFile : requiringFiles) {
- buildError(sourceFile,
- generic.util.Path.toPathString(sourceFile) + " : failed import " +
- OSGiUtils.extractPackageNamesFromFailedResolution(
- requirement.toString()));
- }
- }
- else {
- writer.printf(" %s\n", requirement.toString());
- }
- }
-
- summary.printf("%d missing package import%s:%s", requirements.size(),
- requirements.size() > 1 ? "s" : "",
- requirements.stream()
- .flatMap(
- r -> OSGiUtils.extractPackageNamesFromFailedResolution(r.toString())
- .stream())
- .distinct()
- .collect(Collectors.joining(",")));
+ writeErrorUnresolved(writer, summary, requirements);
}
+
// send the capabilities to phidias
bundleWirings.forEach(bundleJavaManager::addBundleWiring);
return bundleJavaManager;
}
+ private void writeErrorUnresolved(PrintWriter writer, Summary summary,
+ List requirements) {
+
+ writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
+ requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
+ for (BundleRequirement requirement : requirements) {
+ List requiringFiles =
+ requirementToSourceFileMap.get(requirement.toString());
+ if (requiringFiles != null && requiringFiles.size() > 0) {
+ writer.printf(" %s, from %s\n", requirement.toString(),
+ requiringFiles.stream()
+ .map(generic.util.Path::toPathString)
+ .collect(Collectors.joining(",")));
+
+ for (ResourceFile sourceFile : requiringFiles) {
+ buildError(sourceFile,
+ generic.util.Path.toPathString(sourceFile) + " : failed import " +
+ OSGiUtils.extractPackageNamesFromFailedResolution(
+ requirement.toString()));
+ }
+ }
+ else {
+ writer.printf(" %s\n", requirement.toString());
+ }
+ }
+
+ String singularity = requirements.size() > 1 ? "s" : "";
+ String missing = requirements.stream()
+ .flatMap(
+ r -> OSGiUtils.extractPackageNamesFromFailedResolution(r.toString()).stream())
+ .distinct()
+ .collect(Collectors.joining(","));
+ summary.printf("%d missing package import%s:%s", requirements.size(), singularity, missing);
+ }
+
/*
- * Try building sourcefiles.. on success return true.
+ * Try building source files. On success return true.
*
* If build fails, collect errors, remove files that caused
* errors from source files, and return false.
*/
private boolean tryBuild(PrintWriter writer, BundleJavaManager bundleJavaManager,
List sourceFiles, List options) throws IOException {
- DiagnosticCollector diagnostics = new DiagnosticCollector();
+ DiagnosticCollector diagnostics = new DiagnosticCollector<>();
JavaCompiler.CompilationTask task =
compiler.getTask(writer, bundleJavaManager, diagnostics, options, null, sourceFiles);
@@ -812,6 +826,7 @@ public class GhidraSourceBundle extends GhidraBundle {
if (successfulCompilation) {
return true;
}
+
Set filesWithErrors = new HashSet<>();
for (Diagnostic extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
String error = diagnostic.toString() + "\n";
@@ -836,9 +851,9 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * generate a manifest (and an activator)
+ * Generate a manifest (and an activator)
*
- * assumes that {@link #updateRequirementsFromMetadata()} has been called recently
+ * Assumes that {@link #updateRequirementsFromMetadata()} has been called recently
*/
private String generateManifest(PrintWriter writer, Summary summary, Path binaryManifest)
throws OSGiException, IOException {
@@ -849,14 +864,14 @@ public class GhidraSourceBundle extends GhidraBundle {
GhidraSourceBundle.sourceDirHash(getSourceDirectory()));
analyzer.setProperty("Bundle-Version", GENERATED_VERSION);
- if (!importPackageValues.isEmpty()) {
+ if (importPackageValues.isEmpty()) {
+ analyzer.setProperty("Import-Package", "*");
+ }
+ else {
// constrain analyzed imports according to what's declared in @importpackage tags
analyzer.setProperty("Import-Package",
importPackageValues.stream().collect(Collectors.joining(",")) + ",*");
}
- else {
- analyzer.setProperty("Import-Package", "*");
- }
analyzer.setProperty("Export-Package", "!*.private.*,!*.internal.*,*");
@@ -866,44 +881,13 @@ public class GhidraSourceBundle extends GhidraBundle {
manifest = analyzer.calcManifest();
}
catch (Exception e) {
- summary.print("bad manifest");
- throw new OSGiException("failed to calculate manifest by analyzing code", e);
+ summary.print("Bad manifest");
+ throw new OSGiException("Failed to calculate manifest by analyzing code", e);
}
- String activatorClassName = null;
- try {
- for (Clazz clazz : analyzer.getClassspace().values()) {
- if (clazz.is(QUERY.IMPLEMENTS,
- new Instruction("org.osgi.framework.BundleActivator"), analyzer)) {
- System.err.printf("found BundleActivator class %s\n", clazz);
- activatorClassName = clazz.toString();
- }
- }
+ if (!addActivatorClass(writer, analyzer, manifest, summary)) {
+ return summary.getValue();
}
- catch (Exception e) {
- summary.print("failed bnd analysis");
- throw new OSGiException("failed to query classes while searching for activator", e);
- }
-
- Attributes manifestAttributes = manifest.getMainAttributes();
- if (activatorClassName == null) {
- activatorClassName = GENERATED_ACTIVATOR_CLASSNAME;
- if (!buildDefaultActivator(binaryDir, activatorClassName, writer)) {
- summary.print("failed to build generated activator");
- return summary.getValue();
- }
- // since we add the activator after bndtools built the imports, we should add its imports too
- String imps = manifestAttributes.getValue(Constants.IMPORT_PACKAGE);
- if (imps == null) {
- manifestAttributes.putValue(Constants.IMPORT_PACKAGE,
- GhidraBundleActivator.class.getPackageName());
- }
- else {
- manifestAttributes.putValue(Constants.IMPORT_PACKAGE,
- imps + "," + GhidraBundleActivator.class.getPackageName());
- }
- }
- manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
// write the manifest
Files.createDirectories(binaryManifest.getParent());
@@ -917,6 +901,50 @@ public class GhidraSourceBundle extends GhidraBundle {
return summary.getValue();
}
+ private boolean addActivatorClass(PrintWriter writer, Analyzer analyzer, Manifest manifest,
+ Summary summary) throws OSGiException, IOException {
+
+ String activatorClassName = null;
+ try {
+ for (Clazz clazz : analyzer.getClassspace().values()) {
+ if (clazz.is(QUERY.IMPLEMENTS,
+ new Instruction(INSTRCTION_ACTIVATOR), analyzer)) {
+ Msg.trace(this, "Found BundleActivator class " + clazz);
+ activatorClassName = clazz.toString();
+ }
+ }
+ }
+ catch (Exception e) {
+ summary.print("Failed bnd analysis");
+ throw new OSGiException("Failed to query classes while searching for activator", e);
+ }
+
+ Attributes manifestAttributes = manifest.getMainAttributes();
+ if (activatorClassName != null) {
+ manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
+ return true;
+ }
+
+ activatorClassName = GENERATED_ACTIVATOR_CLASSNAME;
+ if (!buildDefaultActivator(binaryDir, activatorClassName, writer)) {
+ summary.print("Failed to build generated activator");
+ return false;
+ }
+
+ // since we add the activator after bndtools built the imports, add its imports too
+ String imports = manifestAttributes.getValue(Constants.IMPORT_PACKAGE);
+ String activatorPackageName = GhidraBundleActivator.class.getPackageName();
+ if (imports == null) {
+ manifestAttributes.putValue(Constants.IMPORT_PACKAGE, activatorPackageName);
+ }
+ else {
+ manifestAttributes.putValue(Constants.IMPORT_PACKAGE, imports + "," +
+ activatorPackageName);
+ }
+ manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
+ return true;
+ }
+
/**
* create and compile a default bundle activator
*
@@ -928,8 +956,8 @@ public class GhidraSourceBundle extends GhidraBundle {
*/
private boolean buildDefaultActivator(Path bindir, String activatorClassName, Writer writer)
throws IOException {
- Path activatorSourceFileName = bindir.resolve(activatorClassName + ".java");
+ Path activatorSourceFileName = bindir.resolve(activatorClassName + ".java");
try (PrintWriter activatorWriter = new PrintWriter(
Files.newBufferedWriter(activatorSourceFileName, Charset.forName("UTF-8")))) {
activatorWriter.println("import " + GhidraBundleActivator.class.getName() + ";");
@@ -959,35 +987,36 @@ public class GhidraSourceBundle extends GhidraBundle {
try (StandardJavaFileManager javaFileManager =
compiler.getStandardFileManager(null, null, null);
- BundleJavaManager bundleJavaManager = new MyBundleJavaManager(
+ BundleJavaManager bundleManager = new MyBundleJavaManager(
bundleHost.getHostFramework(), javaFileManager, options);) {
Iterable extends JavaFileObject> sourceFiles =
javaFileManager.getJavaFileObjectsFromPaths(List.of(activatorSourceFileName));
DiagnosticCollector diagnostics =
- new DiagnosticCollector();
- JavaCompiler.CompilationTask task = compiler.getTask(writer, bundleJavaManager,
+ new DiagnosticCollector<>();
+ JavaCompiler.CompilationTask task = compiler.getTask(writer, bundleManager,
diagnostics, options, null, sourceFiles);
- if (!task.call()) {
- for (Diagnostic extends JavaFileObject> diagnostic : diagnostics
- .getDiagnostics()) {
- writer.write(diagnostic.getSource().toString() + ": " +
- diagnostic.getMessage(null) + "\n");
- }
- return false;
+ if (task.call()) {
+ return true;
}
- return true;
+
+ for (Diagnostic extends JavaFileObject> diagnostic : diagnostics
+ .getDiagnostics()) {
+ writer.write(diagnostic.getSource().toString() + ": " +
+ diagnostic.getMessage(null) + "\n");
+ }
+ return false;
}
}
/**
- * compile a source directory to an exploded bundle
+ * Compile a source directory to an exploded bundle.
*
* @param writer for updating the user during compilation
+ * @return a summary of the work performed
* @throws IOException for source/manifest file reading/generation and binary deletion/creation
* @throws OSGiException if generation of bundle metadata fails
*/
private String compileToExplodedBundle(PrintWriter writer) throws IOException, OSGiException {
- compileAttempted();
Files.createDirectories(binaryDir);
@@ -1047,8 +1076,12 @@ public class GhidraSourceBundle extends GhidraBundle {
}
}
+//=================================================================================================
+// Inner Classes
+//=================================================================================================
+
private static class MyBundleJavaManager extends BundleJavaManager {
- static URL[] EMPTY_URL_ARRAY = new URL[0];
+ private static URL[] EMPTY_URL_ARRAY = new URL[0];
MyBundleJavaManager(Bundle bundle, JavaFileManager javaFileManager, List options)
throws IOException {
@@ -1056,20 +1089,19 @@ public class GhidraSourceBundle extends GhidraBundle {
}
/**
- * since the JavaCompiler tasks can close the class loader returned by this
- * method, make sure we're returning a copy.
+ * Since the JavaCompiler tasks can close the class loader returned by this method, make
+ * sure we're returning a copy.
*/
@Override
public ClassLoader getClassLoader() {
return new URLClassLoader(EMPTY_URL_ARRAY, super.getClassLoader());
}
-
}
private static class Summary {
- static String SEPERATOR = ", ";
- final StringWriter stringWriter = new StringWriter();
- final PrintWriter printWriter = new PrintWriter(stringWriter, true);
+ private static final String SEPERATOR = ", ";
+ private final StringWriter stringWriter = new StringWriter();
+ private final PrintWriter printWriter = new PrintWriter(stringWriter, true);
void printf(String format, Object... args) {
if (stringWriter.getBuffer().length() > 0) {
@@ -1096,12 +1128,12 @@ public class GhidraSourceBundle extends GhidraBundle {
* "B" -> [directory/B.class, directory/B$inner.class]
*
*
- * A list of classes are then processed one at a time with {@link ClassMapper#findAndRemove}.
+ *
A list of classes are then processed with {@link ClassMapper#findAndRemove}.
*
*
After processing, "extras" are handled with {@link ClassMapper#extraClassFiles}.
*/
private static class ClassMapper {
- final Map> classToClassFilesMap;
+ private final Map> classToClassFilesMap;
/**
* Map classes in {@code directory} with {@link ClassMapper}.
@@ -1129,8 +1161,7 @@ public class GhidraSourceBundle extends GhidraBundle {
if (money >= 0) {
return fileName.substring(0, money);
}
- // drop ".class"
- return fileName.substring(0, fileName.length() - 6);
+ return fileName.substring(0, fileName.length() - 6); // drop ".class"
}
List findAndRemove(ResourceFile sourceFile) {
@@ -1139,7 +1170,7 @@ public class GhidraSourceBundle extends GhidraBundle {
return null;
}
- className = className.substring(0, className.length() - 5);
+ className = className.substring(0, className.length() - 5); // drop ".java"
long lastModifiedSource = sourceFile.lastModified();
List classFiles = classToClassFilesMap.remove(className);
if (classFiles == null) {
@@ -1158,17 +1189,15 @@ public class GhidraSourceBundle extends GhidraBundle {
return null;
}
- public boolean hasExtraClassFiles() {
+ boolean hasExtraClassFiles() {
return !classToClassFilesMap.isEmpty();
}
- public Collection extraClassFiles() {
+ Collection extraClassFiles() {
return classToClassFilesMap.values()
.stream()
.flatMap(l -> l.stream())
.collect(Collectors.toList());
}
-
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiUtils.java
index 50e6ced1c5..9b5104bdcd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiUtils.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/OSGiUtils.java
@@ -281,5 +281,4 @@ public class OSGiUtils {
Msg.error(OSGiUtils.class, "Error while collecting packages from jar", e);
}
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java
index e3f3b08ab8..ebfe9e71da 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java
@@ -1166,7 +1166,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
}
- class RefreshingBundleHostListener implements BundleHostListener {
+ private class RefreshingBundleHostListener implements BundleHostListener {
@Override
public void bundleBuilt(GhidraBundle bundle, String summary) {
@@ -1179,8 +1179,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
GhidraSourceBundle sourceBundle = (GhidraSourceBundle) bundle;
ResourceFile sourceDirectory = sourceBundle.getFile();
if (summary == null) {
- // a null summary means the build didn't change anything,
- // so use any errors from the last build
+ // a null summary means the build didn't change anything, so use any errors from
+ // the last build
for (ResourceFile sourceFile : sourceBundle.getAllErrors().keySet()) {
if (sourceFile.getParentFile().equals(sourceDirectory)) {
ScriptInfo scriptInfo = infoManager.getScriptInfo(sourceFile);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java
index 6099eceeef..382b7c67aa 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java
@@ -83,7 +83,7 @@ public class GhidraScriptUtil {
}
/**
- * initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths.
+ * Initialize state of GhidraScriptUtil with user, system, and optional extra system paths.
*
* @param aBundleHost the host to use
* @param extraSystemPaths additional system paths for this run, can be null
@@ -106,7 +106,7 @@ public class GhidraScriptUtil {
*/
public static void dispose() {
if (bundleHost != null) {
- bundleHost.dispose();
+ bundleHost.stopFramework();
bundleHost = null;
}
providers = null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java
index 5c1e34d5c2..a958cd6c8b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java
@@ -121,8 +121,8 @@ public class JavaScriptProvider extends GhidraScriptProvider {
throw new ClassNotFoundException(
"Failed to find source bundle containing script: " + sourceFile.toString());
}
- bundleHost.activateAll(Collections.singletonList(bundle), TaskMonitor.DUMMY, writer);
+ bundleHost.activateAll(Collections.singletonList(bundle), TaskMonitor.DUMMY, writer);
String classname = bundle.classNameForScript(sourceFile);
Class> clazz = bundle.getOSGiBundle().loadClass(classname); // throws ClassNotFoundException
return clazz;
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java
index 349ef1ab6f..9e7a070568 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleHostTest.java
@@ -87,7 +87,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
@After
public void tearDown() {
- bundleHost.dispose();
+ bundleHost.stopFramework();
capturingBundleHostListener = null;
bundleHost = null;
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java
index 29b9c33716..6a7a075dfe 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java
@@ -435,11 +435,11 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
}
void awaitActivation() throws InterruptedException {
- assertTrue(activationLatch.await(5000, TimeUnit.MILLISECONDS));
+ assertTrue(activationLatch.await(30000, TimeUnit.MILLISECONDS));
}
void awaitDisablement() throws InterruptedException {
- assertTrue(disablementLatch.await(5000, TimeUnit.MILLISECONDS));
+ assertTrue(disablementLatch.await(30000, TimeUnit.MILLISECONDS));
}
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java
index 2941477638..7a9f5b9f90 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java
@@ -18,6 +18,7 @@ package ghidra.app.plugin.core.script;
import static org.junit.Assert.*;
import java.io.*;
+import java.nio.file.Path;
import org.apache.logging.log4j.*;
import org.apache.logging.log4j.core.config.Configurator;
@@ -150,7 +151,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// remove all class files from the user script bin dir
File userScriptsBinDir =
- GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
+ getBinDirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
File[] userScriptBinDirFiles;
if (userScriptsBinDir.exists()) {
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
@@ -195,7 +196,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// verify that the generated class file is placed in the default scripting home/bin
File userScriptsBinDir =
- GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
+ getBinDirFromScriptFile(systemScriptFile).toFile();
String className = scriptName.replace(".java", ".class");
File expectedClassFile = new File(userScriptsBinDir, className);
@@ -232,7 +233,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
waitForScriptCompletion(scriptID, 20000);
// verify a bin dir was created and that the class file is in it
- File binDir = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile();
+ File binDir = getBinDirFromScriptFile(newScriptFile).toFile();
assertTrue("bin output dir not created", binDir.exists());
File scriptClassFile = new File(binDir, rawScriptName + ".class");
@@ -493,4 +494,11 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
assertContainsText("The field of the script still has state--the script was not recreated",
"*2*", output);
}
+
+ private Path getBinDirFromScriptFile(ResourceFile sourceFile) {
+ ResourceFile tmpSourceDir = sourceFile.getParentFile();
+ String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
+ return GhidraSourceBundle.getCompiledBundlesDir().resolve(tmpSymbolicName);
+ }
+
}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/JavaCompiler.java b/Ghidra/Features/Base/src/test/java/ghidra/test/JavaCompiler.java
index 9ded6b8a91..82b2f7b82b 100644
--- a/Ghidra/Features/Base/src/test/java/ghidra/test/JavaCompiler.java
+++ b/Ghidra/Features/Base/src/test/java/ghidra/test/JavaCompiler.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,69 +23,62 @@ import java.io.*;
*
*
*/
+@Deprecated(forRemoval = true, since = "10.2") // This is not used
public class JavaCompiler {
private IOThread cmdOut;
private IOThread cmdErr;
- /**
- * Compile a java file.
- */
- public void compile(File javaFile) {
+ public void compile(File javaFile) {
String name = javaFile.getName();
String className = name.substring(0, name.indexOf(".")) + ".class";
-
+
File parent = javaFile.getParentFile();
String parentPath = parent.getAbsolutePath();
int pos = parentPath.lastIndexOf("ghidra");
- String destPath = parentPath.substring(0, pos-1);
-
+ String destPath = parentPath.substring(0, pos - 1);
+
javaFile.deleteOnExit();
-
+
File classFile = new File(parent, className);
classFile.deleteOnExit();
-
+
String classpath = System.getProperty("java.class.path");
String javaLoc = System.getProperty("java.home");
if (javaLoc.endsWith("jre")) {
- javaLoc = javaLoc.substring(0, javaLoc.indexOf("jre")-1);
+ javaLoc = javaLoc.substring(0, javaLoc.indexOf("jre") - 1);
}
String argV[] = new String[6];
- argV[0] = javaLoc + File.separator + "bin" + File.separator +"javac";
+ argV[0] = javaLoc + File.separator + "bin" + File.separator + "javac";
argV[1] = "-classpath";
argV[2] = classpath;
argV[3] = "-d";
argV[4] = destPath;
- argV[5] = javaFile.getAbsolutePath();
+ argV[5] = javaFile.getAbsolutePath();
try {
Process p = Runtime.getRuntime().exec(argV);
- for (int i=0; iFor example, given, in this order, two files with these paths
* /a/b
and /a/b/c
, this method will return 'c'.
*
* @param f1 the parent resource file
* @param f2 the child resource file
- * @return the relative path of {@code f2} in {@code f1}
+ * @return the relative path of {@code f2} in {@code f1}; null if f1 is not a parent of f2
*/
public static String relativizePath(ResourceFile f1, ResourceFile f2) {
StringBuilder sb = new StringBuilder(f2.getName());