All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.plugin.CommonJSPlugin Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.plugin;

import cn.nukkit.Nukkit;
import cn.nukkit.Server;
import cn.nukkit.api.Since;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.event.Listener;
import cn.nukkit.plugin.js.*;
import cn.nukkit.utils.Config;
import cn.nukkit.utils.PluginException;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;

public class CommonJSPlugin implements Plugin, Listener {

    public static final Int2ObjectOpenHashMap jsPluginIdMap = new Int2ObjectOpenHashMap<>();
    public static final ConcurrentHashMap jsExternalMap = new ConcurrentHashMap<>();
    public static int globalMaxId = 0;

    protected String pluginName;
    protected File pluginDir;
    protected File mainJSFile;
    protected Server server;

    private boolean isEnabled = false;
    private boolean initialized = false;

    private PluginDescription description;
    private JSPluginLoader jsPluginLoader;
    private PluginLogger logger;

    protected JSClassLoader classLoader;
    protected ESMFileSystem fileSystem;
    protected Context jsContext = null;
    protected Value jsExports = null;
    public final LinkedHashMap usedFeatures = new LinkedHashMap<>(0);

    public final int id = globalMaxId++;

    public final void init(@NotNull JSPluginLoader jsPluginLoader, File pluginDir, PluginDescription pluginDescription) {
        this.jsPluginLoader = jsPluginLoader;
        this.server = jsPluginLoader.server;
        this.pluginDir = pluginDir;
        this.mainJSFile = new File(pluginDir, pluginDescription.getMain());
        if (!mainJSFile.exists()) {
            try {
                //noinspection ResultOfMethodCallIgnored
                mainJSFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        this.pluginName = pluginDescription.getName();
        this.description = pluginDescription;
        this.logger = new PluginLogger(this);
        for (var each : description.getFeatures()) {
            var feature = JSFeatures.getFeature(each);
            if (feature == null) {
                throw new PluginException("Feature " + each + " requested by " + pluginName + " not found!");
            }
            usedFeatures.put(each, feature);
        }
        var cbd = Context.newBuilder("js")
                .hostClassLoader(classLoader = new JSClassLoader(this, Thread.currentThread().getContextClassLoader()))
                .fileSystem(fileSystem = new ESMFileSystem(pluginDir, this, classLoader))
                .allowAllAccess(true)
                .allowHostAccess(HostAccess.newBuilder(HostAccess.ALL).targetTypeMapping(Double.class, Float.class, null, Double::floatValue).build())
                .allowHostClassLoading(true)
                .allowHostClassLookup(className -> true)
                .allowIO(true)
                .allowExperimentalOptions(true)
                .option("js.esm-eval-returns-exports", "true")
                .option("js.shared-array-buffer", "true")
                .option("js.foreign-object-prototype", "true")
                .option("js.nashorn-compat", "true")
                .option("js.ecmascript-version", "13");
        if (Nukkit.CHROME_DEBUG_PORT != -1 && Nukkit.JS_DEBUG_LIST.contains(description.getName())) {
            cbd.option("inspect", String.valueOf(Nukkit.CHROME_DEBUG_PORT))
                    .option("inspect.Path", description.getName())
                    .option("inspect.Suspend", "true")
                    .option("inspect.Internal", "true")
                    .option("inspect.SourcePath", pluginDir.getAbsolutePath());
        }
        jsContext = cbd.build();
        JSIInitiator.init(jsContext);
        jsContext.getBindings("js").putMember("console", new JSProxyLogger(logger));
        jsPluginIdMap.put(id, this);
        for (var each : usedFeatures.values()) {
            if (each.needsInject()) {
                each.injectIntoContext(jsContext);
            }
        }
        this.initialized = true;
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        return false;
    }

    @Override
    public void onLoad() {
        try {
            jsExports = jsContext.eval(Source.newBuilder("js", mainJSFile)
                    .name("@" + description.getName() + "/" + mainJSFile.getName())
                    .mimeType("application/javascript+module").build());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("SynchronizeOnNonFinalField")
    @Override
    public void onEnable() {
        var mainFunc = jsExports.getMember("main");
        isEnabled = true;
        try {
            if (mainFunc != null && mainFunc.canExecute()) {
                synchronized (jsContext) {
                    mainFunc.executeVoid();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            isEnabled = false;
        }
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }

    @SuppressWarnings("SynchronizeOnNonFinalField")
    @Override
    public void onDisable() {
        var closeFunc = jsExports.getMember("close");
        if (closeFunc != null && closeFunc.canExecute()) {
            synchronized (jsContext) {
                closeFunc.executeVoid();
            }
            JSIInitiator.closeContext(jsContext);
            synchronized (jsContext) {
                jsContext.close();
            }
        }
        isEnabled = false;
        jsPluginIdMap.remove(id);
    }

    @Override
    public boolean isDisabled() {
        return !isEnabled;
    }

    @Override
    public File getDataFolder() {
        return null;
    }

    @Override
    public PluginDescription getDescription() {
        return description;
    }

    @Override
    public InputStream getResource(String filename) {
        return null;
    }

    @Override
    public boolean saveResource(String filename) {
        return false;
    }

    @Override
    public boolean saveResource(String filename, boolean replace) {
        return false;
    }

    @Override
    public boolean saveResource(String filename, String outputName, boolean replace) {
        return false;
    }

    @Override
    public Config getConfig() {
        return null;
    }

    @Override
    public void saveConfig() {

    }

    @Override
    public void saveDefaultConfig() {

    }

    @Override
    public void reloadConfig() {

    }

    @Override
    public Server getServer() {
        return server;
    }

    @Override
    public String getName() {
        return pluginName;
    }

    @Override
    public PluginLogger getLogger() {
        return logger;
    }

    @Override
    public PluginLoader getPluginLoader() {
        return jsPluginLoader;
    }

    @Since("1.19.50-r3")
    @Override
    public File getFile() {
        return pluginDir;
    }

    public boolean isInitialized() {
        return initialized;
    }

    public Context getJsContext() {
        return jsContext;
    }

    public Value getJsExports() {
        return jsExports;
    }

    public ESMFileSystem getFileSystem() {
        return fileSystem;
    }

    public JSClassLoader getClassLoader() {
        return classLoader;
    }

    public CommonJSPlugin setClassLoader(JSClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy