Skip to content

Commit

Permalink
Plugin unloading
Browse files Browse the repository at this point in the history
  • Loading branch information
xxDark committed Apr 28, 2024
1 parent 51902e9 commit 98b54f8
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 6 deletions.
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ gradle-checker-processor = "2.0.2"
javafx-plugin = "0.1.0"
shadow = "8.1.1"
peterabeles-gversion = "1.10.2"
jgrapht = "1.5.2"

[libraries]


asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" }
asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
Expand Down Expand Up @@ -124,6 +124,8 @@ vineflower = { module = "org.vineflower:vineflower", version.ref = "vineflower"

wordwrap = { module = "com.github.davidmoten:word-wrap", version.ref = "wordwrap" }

jgrapht = { module = "org.jgrapht:jgrapht-core", version.ref = "jgrapht" }

[bundles]
asm = [
"asm-core",
Expand Down
1 change: 1 addition & 0 deletions recaf-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
api(libs.bundles.jasm)
api(libs.vineflower)
api(libs.wordwrap)
api(libs.jgrapht)
}

// Force generation of gversion data class when the version information is not up-to-date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public Collection<PluginContainer<?>> loadPlugins(@Nonnull PluginDiscoverer disc
return mainGraph.apply(prepared);
}

@Nonnull
@Override
public PluginUnloader unloadPlugin(@Nonnull String id) {
return mainGraph.unload(id);
}

@Override
public boolean isPluginLoaded(@Nonnull String id) {
return mainGraph.getContainer(id) != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

final class PluginGraph {
final Map<String, LoadedPlugin> plugins = HashMap.newHashMap(16);
Expand All @@ -26,8 +29,8 @@ final class PluginGraph {
this.classAllocator = classAllocator;
}

// FIXME pull in jgrapht (or make a small alternative). This is a mess.
Collection<PluginContainer<?>> apply(List<PreparedPlugin> preparedPlugins) throws PluginException {
@Nonnull
Collection<PluginContainer<?>> apply(@Nonnull List<PreparedPlugin> preparedPlugins) throws PluginException {
Map<String, LoadedPlugin> temp = LinkedHashMap.newLinkedHashMap(preparedPlugins.size());
var plugins = this.plugins;
for (var preparedPlugin : preparedPlugins) {
Expand Down Expand Up @@ -88,6 +91,86 @@ Collection<PluginContainer<?>> apply(List<PreparedPlugin> preparedPlugins) throw
return Collections2.transform(temp.values(), input -> input.container);
}

@Nonnull
PluginUnloader unload(@Nonnull String id) {
LoadedPlugin plugin = plugins.get(id);
if (plugin == null) {
throw new IllegalStateException("Plugin %s is not loaded".formatted(id));
}
Map<LoadedPlugin, Set<LoadedPlugin>> dependants = HashMap.newHashMap(8);
collectDependants(plugin, dependants);
return new PluginUnloader() {
@Override
public void commit() throws PluginException {
PluginException ex = unload(plugin, dependants);
if (ex != null)
throw ex;
}

@Nonnull
@Override
public PluginInfo unloadingPlugin() {
return plugin.container.info();
}

@Nonnull
@Override
public Stream<PluginInfo> dependants() {
return dependants.values()
.stream()
.flatMap(Collection::stream)
.map(plugin -> plugin.container.info());
}
};
}

private PluginException unload(LoadedPlugin plugin, Map<LoadedPlugin, Set<LoadedPlugin>> dependants) {
String id = plugin.container.info().id();
if (!plugins.remove(id, plugin)) {
throw new IllegalStateException("Plugin %s was already removed, recursion?".formatted(id));
}
PluginException exception = null;
for (LoadedPlugin dependant : dependants.get(plugin)) {
PluginException inner = unload(dependant, dependants);
if (inner != null) {
if (exception == null) {
exception = inner;
} else {
exception.addSuppressed(inner);
}
}
}
try {
PluginContainerImpl<?> container = plugin.container;
try {
container.plugin().onDisable();
} finally {
if (container.classLoader instanceof AutoCloseable ac) {
ac.close();
}
}
} catch (Exception ex) {
PluginException pex = new PluginException(ex);
if (exception == null) {
exception = pex;
} else {
exception.addSuppressed(pex);
}
}
return exception;
}

private void collectDependants(LoadedPlugin plugin, Map<LoadedPlugin, Set<LoadedPlugin>> dependants) {
Set<LoadedPlugin> dependantsSet = dependants.computeIfAbsent(plugin, __ -> HashSet.newHashSet(4));
for (LoadedPlugin pl : plugins.values()) {
if (plugin == pl) continue;
if (pl.dependencies.contains(plugin)) {
dependantsSet.add(pl);
collectDependants(pl, dependants);
}
}
}

@Nullable
PluginContainer<?> getContainer(@Nonnull String id) {
LoadedPlugin plugin = plugins.get(id);
Expand All @@ -97,6 +180,7 @@ PluginContainer<?> getContainer(@Nonnull String id) {
return plugin.container;
}

@Nonnull
Collection<PluginContainer<?>> plugins() {
return Collections2.transform(plugins.values(), plugin -> plugin.container);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,15 @@ default <T> Collection<T> getPluginsOfType(@Nonnull Class<T> type) {
*/
@Nonnull
Collection<PluginContainer<?>> loadPlugins(@Nonnull PluginDiscoverer discoverer) throws PluginException;


/**
* @param id ID of the plugin to be unloaded.
* @return Plugin unload action.
* @see PluginUnloader
* @throws IllegalStateException
* If plugin to be unloaded was not found.
*/
@Nonnull
PluginUnloader unloadPlugin(@Nonnull String id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package software.coley.recaf.services.plugin;

import jakarta.annotation.Nonnull;
import software.coley.recaf.plugin.PluginException;
import software.coley.recaf.plugin.PluginInfo;

import java.util.stream.Stream;

/**
* Plugin unload action.
* @author xDark
*/
public interface PluginUnloader {

/**
* Unloads the plugins.
* @throws PluginException
* If any of the plugins failed to unload.
*/
void commit() throws PluginException;

/**
* @return Plugin to be unloaded.
*/
@Nonnull
PluginInfo unloadingPlugin();

/**
* @return A collection of plugins that depend on the plugin to be unloaded.
* @apiNote These plugins will also get unloaded.
*/
@Nonnull
Stream<PluginInfo> dependants();
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ public String[] softDependencies() {
assertEquals(1, pluginManager.getPlugins().size());
assertSame(container, pluginManager.getPlugin(id));

/* // Now unload it
pluginManager.unloadPlugin(container);
// Now unload it
pluginManager.unloadPlugin(id).commit();

// Assert the plugin is no longer active
assertEquals(0, pluginManager.getPlugins().size());
assertNull(pluginManager.getPlugin(id));*/
assertNull(pluginManager.getPlugin(id));
} catch (PluginException ex) {
fail("Failed to load plugin", ex);
}
Expand Down

0 comments on commit 98b54f8

Please sign in to comment.