Skip to content

Commit

Permalink
refactor: a big refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd committed Jan 6, 2025
1 parent 5a6e729 commit ee6a365
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 33 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/allaymc/scriptpluginext/ScriptPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.allaymc.scriptpluginext;

/**
* @author daoge_cmd
*/
public interface ScriptPlugin {
boolean canResetContext();

void resetContext();
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package org.allaymc.scriptpluginext;

import lombok.Getter;
import org.allaymc.server.plugin.SimplePluginDescriptor;
import org.allaymc.api.plugin.PluginDescriptor;

/**
* @author daoge_cmd
*/
@SuppressWarnings("FieldMayBeFinal")
@Getter
public class ScriptPluginDescriptor extends SimplePluginDescriptor {
private int debugPort = -1;
public interface ScriptPluginDescriptor extends PluginDescriptor {
int getDebugPort();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.allaymc.scriptpluginext;

import org.allaymc.api.registry.Registries;
import org.allaymc.scriptpluginext.js.JSPluginLoader;
import org.allaymc.scriptpluginext.js.JSPluginSource;
import org.allaymc.server.extension.Extension;
import org.allaymc.server.plugin.AllayPluginManager;

Expand All @@ -10,6 +12,12 @@
public class ScriptPluginExtension extends Extension {
@Override
public void main(String[] args) {
AllayPluginManager.registerSource(new JSPluginSource());
AllayPluginManager.registerLoaderFactory(new JSPluginLoader.JsPluginLoaderFactory());
}
}

@Override
public void afterServerStarted() {
Registries.COMMANDS.register(new ScriptPluginExtensionCommand());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.allaymc.scriptpluginext;

import org.allaymc.api.command.SimpleCommand;
import org.allaymc.api.command.tree.CommandContext;
import org.allaymc.api.command.tree.CommandTree;
import org.allaymc.api.plugin.PluginContainer;
import org.allaymc.api.server.Server;

/**
* @author daoge_cmd
*/
public class ScriptPluginExtensionCommand extends SimpleCommand {
public ScriptPluginExtensionCommand() {
super("se", "The main command of the script plugin extension.");
}

@Override
public void prepareCommandTree(CommandTree tree) {
tree.getRoot()
.key("resetctx")
.str("plugin")
.optional()
.exec(context -> {
String pluginName = context.getResult(1);
if (pluginName.isBlank()) {
// Reset ctx of all script plugins
Server.getInstance().getPluginManager().getEnabledPlugins().forEach((name, container) -> tryResetContextOf(context, name, container));
} else {
var container = Server.getInstance().getPluginManager().getEnabledPlugin(pluginName);
if (container == null) {
context.addError("Plugin not found: " + pluginName);
return context.fail();
}

tryResetContextOf(context, pluginName, container);
}
context.addOutput("Done");
return context.success();
});
}

protected void tryResetContextOf(CommandContext context, String name, PluginContainer container) {
var plugin = container.plugin();
if (plugin instanceof ScriptPlugin sp && sp.canResetContext()) {
context.addOutput("Resetting context of " + name);
try {
sp.resetContext();
} catch (Throwable t) {
context.addError("Failed to reset context of " + name, t);
}
}
}
}
80 changes: 63 additions & 17 deletions src/main/java/org/allaymc/scriptpluginext/js/JSPlugin.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.allaymc.scriptpluginext.js;

import lombok.SneakyThrows;
import org.allaymc.scriptpluginext.ScriptPlugin;
import org.allaymc.scriptpluginext.ScriptPluginDescriptor;
import org.allaymc.api.plugin.Plugin;
import org.allaymc.api.plugin.PluginContainer;
Expand All @@ -10,7 +11,7 @@
/**
* @author daoge_cmd
*/
public class JSPlugin extends Plugin {
public class JSPlugin extends Plugin implements ScriptPlugin {

protected Context context;
protected Value export;
Expand All @@ -25,24 +26,41 @@ public void setPluginContainer(PluginContainer pluginContainer) {
@SneakyThrows
@Override
public void onLoad() {
// ClassCastException won't happen
var chromeDebugPort = ((ScriptPluginDescriptor) pluginContainer.descriptor()).getDebugPort();
var debugPort = ((ScriptPluginDescriptor) pluginContainer.descriptor()).getDebugPort();
var cbd = Context.newBuilder("js")
.allowIO(IOAccess.ALL)
.allowAllAccess(true)
.allowHostAccess(HostAccess.ALL)
.allowHostClassLoading(true)
.allowHostClassLookup(className -> true)
.allowExperimentalOptions(true)
.option("js.esm-eval-returns-exports", "true");
if (chromeDebugPort > 0) {
pluginLogger.info("Debug mode for javascript plugin {} is enabled. Port: {}", pluginContainer.descriptor().getName(), chromeDebugPort);
// Use strict mode by default
.option("js.strict", "true")
// The js.esm-eval-returns-exports option (false by default) can be used to expose
// the ES module namespace exported object to a Polyglot Context. This can be handy
// when an ES module is used directly from Java
.option("js.esm-eval-returns-exports", "true")
// Enable CommonJS experimental support.
.option("js.commonjs-require", "true")
// Directory where the NPM modules to be loaded are located.
.option("js.commonjs-require-cwd", pluginContainer.loader().getPluginPath().toString());
// TODO: js.commonjs-core-modules-replacements
if (debugPort > 0) {
pluginLogger.info("Debug mode for javascript plugin {} is enabled. Port: {}", pluginContainer.descriptor().getName(), debugPort);
// Debug mode is enabled
cbd.option("inspect", String.valueOf(chromeDebugPort))
cbd.option("inspect", String.valueOf(debugPort))
// The custom path that generates the connection URL
.option("inspect.Path", pluginContainer.descriptor().getName())
.option("inspect.Suspend", "true")
.option("inspect.Internal", "true")
.option("inspect.SourcePath", pluginContainer.loader().getPluginPath().toFile().getAbsolutePath());
// The list of directories or ZIP/JAR files representing the source path. When the inspected
// application contains relative references to source files, their content is loaded from
// locations resolved with respect to this source path. It is useful during LLVM debugging,
// for instance. The paths are delimited by : on UNIX systems and by ; on MS Windows
.option("inspect.SourcePath", pluginContainer.loader().getPluginPath().toFile().getAbsolutePath())
// Do not suspend on the first line of the application code
.option("inspect.Suspend", "false")
// When true, internal sources are inspected as well. Internal sources may provide
// language implementation details
.option("inspect.Internal", "true");
}
context = cbd.build();
initGlobalMembers();
Expand All @@ -51,45 +69,73 @@ public void onLoad() {
export = context.eval(
Source.newBuilder("js", path.toFile())
.name(entranceJsFileName)
// ECMAScript modules can be loaded in a Context simply by evaluating the module sources. GraalJS loads
// ECMAScript modules based on their file extension. Therefore, any ECMAScript module should have file name
// extension .mjs. Alternatively, the module Source should have MIME type "application/javascript+module"
.mimeType("application/javascript+module")
.build()
);
tryCallJsFunction("onLoad");
tryCallJSFunction("onLoad");
}

@Override
public void onEnable() {
tryCallJsFunction("onEnable");
tryCallJSFunction("onEnable");
}

@Override
public void onDisable() {
tryCallJsFunction("onDisable");
tryCallJSFunction("onDisable");
context.close(true);
}

@Override
public boolean isReloadable() {
return true;
return tryCallJSFunction("isReloadable", false);
}

@Override
public void reload() {
tryCallJSFunction("reload");
}

@Override
public boolean canResetContext() {
return tryCallJSFunction("canResetContext", false);
}

@Override
public void resetContext() {
onDisable();
onLoad();
onEnable();
}

protected void initGlobalMembers() {
var binding = context.getBindings("js");
binding.putMember("plugin", this);
binding.putMember("thisPlugin", this);
// Proxy the original "console" object, so when the js plug-in uses the
// "console" object, it will output information through log4j
binding.putMember("console", proxyLogger);
}

protected void tryCallJsFunction(String functionName) {
var func = export.getMember(functionName);
protected void tryCallJSFunction(String name) {
var func = export.getMember(name);
if (func != null && func.canExecute()) {
func.executeVoid();
}
}

protected <T> T tryCallJSFunction(String name, T defaultValue) {
var func = export.getMember(name);
if (func != null && func.canExecute()) {
try {
return func.execute().asHostObject();
} catch (Throwable ignore) {
return defaultValue;
}
}

return defaultValue;
}
}
17 changes: 8 additions & 9 deletions src/main/java/org/allaymc/scriptpluginext/js/JSPluginLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.allaymc.scriptpluginext.ScriptPluginDescriptor;
import org.allaymc.api.plugin.*;
import org.allaymc.scriptpluginext.ScriptPluginI18nLoader;
import org.allaymc.api.i18n.I18n;
import org.allaymc.api.plugin.PluginContainer;
import org.allaymc.api.plugin.PluginDescriptor;
import org.allaymc.api.plugin.PluginException;
import org.allaymc.api.plugin.PluginLoader;
import org.allaymc.api.utils.JSONUtils;
import org.allaymc.server.i18n.AllayI18n;
import org.allaymc.server.plugin.DefaultPluginSource;

import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -35,7 +30,7 @@ public JSPluginLoader(Path pluginPath) {
@SneakyThrows
@Override
public PluginDescriptor loadDescriptor() {
descriptor = JSONUtils.from(Files.newBufferedReader(pluginPath.resolve("plugin.json")), ScriptPluginDescriptor.class);
this.descriptor = JSONUtils.from(Files.newBufferedReader(pluginPath.resolve("package.json")), PackageJson.class);
PluginDescriptor.checkDescriptorValid(descriptor);
return descriptor;
}
Expand All @@ -53,15 +48,19 @@ public PluginContainer loadPlugin() {
return PluginContainer.createPluginContainer(
new JSPlugin(),
descriptor, this,
DefaultPluginSource.getOrCreateDataFolder(descriptor.getName())
JSPluginSource.getOrCreateDataFolder(descriptor.getName())
);
}

public static class JsPluginLoaderFactory implements PluginLoader.Factory {

@Override
public boolean canLoad(Path pluginPath) {
return pluginPath.getFileName().toString().endsWith(".js") && Files.isDirectory(pluginPath);
var packageJsonPath = pluginPath.resolve("package.json");
return pluginPath.getParent().endsWith(JSPluginSource.JS_PLUGIN_FOLDER) &&
Files.isDirectory(pluginPath) &&
Files.exists(packageJsonPath) &&
Files.isRegularFile(packageJsonPath);
}

@Override
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/org/allaymc/scriptpluginext/js/JSPluginSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.allaymc.scriptpluginext.js;

import lombok.SneakyThrows;
import org.allaymc.api.plugin.PluginSource;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;

/**
* @author daoge_cmd
*/
public class JSPluginSource implements PluginSource {

public static final Path JS_PLUGIN_FOLDER = Path.of("jsplugins");
// Because the js plugin itself is also a folder, set the root of the data folder to another directory to avoid conflicts.
public static final Path JS_PLUGIN_DATA_FOLDER = Path.of("jsplugindata");

@SneakyThrows
public JSPluginSource() {
if (!Files.exists(JS_PLUGIN_FOLDER)) {
Files.createDirectory(JS_PLUGIN_FOLDER);
}
if (!Files.exists(JS_PLUGIN_DATA_FOLDER)) {
Files.createDirectory(JS_PLUGIN_DATA_FOLDER);
}
}

@SneakyThrows
public static Path getOrCreateDataFolder(String pluginName) {
var dataFolder = JS_PLUGIN_DATA_FOLDER.resolve(pluginName);
if (!Files.exists(dataFolder)) {
Files.createDirectory(dataFolder);
}
return dataFolder;
}

@SneakyThrows
@Override
public Set<Path> find() {
try (var stream = Files.list(JS_PLUGIN_FOLDER)) {
return stream.collect(Collectors.toSet());
}
}
}
Loading

0 comments on commit ee6a365

Please sign in to comment.