/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.managed.scripting; import com.jcwhatever.nucleus.Nucleus; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_ActionBar; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Depends; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Economy; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Events; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Include; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Inventory; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_ItemBank; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Items; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Jails; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Locations; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Msg; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_NpcProvider; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Permissions; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Regions; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_ResourcePacks; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Scheduler; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Scoreboard; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Sounds; import com.jcwhatever.nucleus.internal.managed.scripting.api.SAPI_Titles; import com.jcwhatever.nucleus.internal.managed.scripting.api.views.SAPI_Views; import com.jcwhatever.nucleus.internal.managed.scripting.items.InternalScriptItemManager; import com.jcwhatever.nucleus.internal.managed.scripting.locations.InternalScriptLocationManager; import com.jcwhatever.nucleus.internal.managed.scripting.regions.InternalScriptRegionManager; import com.jcwhatever.nucleus.internal.managed.scripting.regions.InternalScriptRegionManagerWrapper; import com.jcwhatever.nucleus.managed.messaging.IMessenger; import com.jcwhatever.nucleus.managed.scheduler.Scheduler; import com.jcwhatever.nucleus.managed.scripting.IEvaluatedScript; import com.jcwhatever.nucleus.managed.scripting.IScript; import com.jcwhatever.nucleus.managed.scripting.IScriptApi; import com.jcwhatever.nucleus.managed.scripting.IScriptFactory; import com.jcwhatever.nucleus.managed.scripting.IScriptManager; import com.jcwhatever.nucleus.managed.scripting.SimpleScriptApi; import com.jcwhatever.nucleus.managed.scripting.SimpleScriptApi.IApiObjectCreator; import com.jcwhatever.nucleus.managed.scripting.items.IScriptItemManager; import com.jcwhatever.nucleus.managed.scripting.locations.IScriptLocationManager; import com.jcwhatever.nucleus.managed.scripting.regions.IScriptRegionManager; import com.jcwhatever.nucleus.mixins.IDisposable; import com.jcwhatever.nucleus.providers.storage.DataStorage; import com.jcwhatever.nucleus.storage.DataPath; import com.jcwhatever.nucleus.storage.IDataNode; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.ScriptUtils; import com.jcwhatever.nucleus.utils.file.FileUtils.DirectoryTraversal; import org.bukkit.plugin.Plugin; import javax.annotation.Nullable; import javax.script.ScriptEngineManager; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * NucleusFramework's default ScriptManager. */ public final class InternalScriptManager implements IScriptManager { private static IScriptFactory _scriptFactory = new IScriptFactory() { @Override public IScript create(String name, @Nullable File file, String type, String script) { return new Script(name, file, type, script); } }; private final File _scriptFolder; private final File _includeFolder; // key is script name private final Map<String, IScript> _scripts = new HashMap<>(25); // key is script name private final Map<String, IEvaluatedScript> _evaluated = new HashMap<>(25); // default script apis included in all evaluated scripts private final List<IScriptApi> _api = new ArrayList<>(15); // global managers private InternalScriptItemManager _itemManager; private InternalScriptLocationManager _locationManager; private InternalScriptRegionManager _regionManager; // plugin context managers private Map<Plugin, InternalScriptItemManager> _itemManagers; private Map<Plugin, InternalScriptLocationManager> _locationManagers; private Map<Plugin, InternalScriptRegionManager> _regionManagers; /** * Constructor. * * @param scriptFolder The folder where scripts are kept. */ public InternalScriptManager(File scriptFolder) { PreCon.notNull(scriptFolder); _scriptFolder = scriptFolder; _includeFolder = new File(scriptFolder, "includes"); if (!_includeFolder.exists() && !_includeFolder.mkdirs()) { throw new RuntimeException("Failed to create script includes folder."); } loadDefaultApi(); } @Override public File getScriptFolder() { return _scriptFolder; } @Override public File getIncludeFolder() { return _includeFolder; } @Override public ScriptEngineManager getEngineManager() { return Nucleus.getScriptEngineManager(); } @Override public IScriptItemManager getItems() { if (_itemManager == null) { IDataNode dataNode = DataStorage.get( Nucleus.getPlugin(), new DataPath("script-items")); dataNode.load(); _itemManager = new InternalScriptItemManager(dataNode); } return _itemManager; } @Override public IScriptItemManager getItems(Plugin plugin) { PreCon.notNull(plugin); if (plugin.equals(Nucleus.getPlugin())) return getItems(); if (_itemManagers == null) _itemManagers = new HashMap<>(10); InternalScriptItemManager manager = _itemManagers.get(plugin); if (manager == null) { IDataNode dataNode = DataStorage.get( plugin, new DataPath("nucleus.script-items")); dataNode.load(); manager = new InternalScriptItemManager(dataNode); _itemManagers.put(plugin, manager); } return manager; } @Override public IScriptLocationManager getLocations() { if (_locationManager == null) { IDataNode dataNode = DataStorage.get( Nucleus.getPlugin(), new DataPath("script-locations")); dataNode.load(); _locationManager = new InternalScriptLocationManager(dataNode); } return _locationManager; } @Override public IScriptLocationManager getLocations(Plugin plugin) { PreCon.notNull(plugin); if (plugin.equals(Nucleus.getPlugin())) return getLocations(); if (_locationManagers == null) _locationManagers = new HashMap<>(10); InternalScriptLocationManager manager = _locationManagers.get(plugin); if (manager == null) { IDataNode dataNode = DataStorage.get( Nucleus.getPlugin(), new DataPath("nucleus.script-locations")); dataNode.load(); manager = new InternalScriptLocationManager(dataNode); _locationManagers.put(plugin, manager); } return manager; } @Override public IScriptRegionManager getRegions() { return new InternalScriptRegionManagerWrapper(Nucleus.getPlugin()); } /** * Get a direct reference to the global region manager. * * <p>The instance goes out of scope when scripts are reloaded and * should not be stored. Use {@link #getRegions()} if a cached reference * needs to be held.</p> */ public InternalScriptRegionManager getRegionsDirect() { if (_regionManager == null) { IDataNode dataNode = DataStorage.get( Nucleus.getPlugin(), new DataPath("script-regions")); dataNode.load(); _regionManager = new InternalScriptRegionManager(dataNode); } return _regionManager; } @Override public IScriptRegionManager getRegions(Plugin plugin) { return new InternalScriptRegionManagerWrapper(plugin); } /** * Get a direct reference to the region manager for the specified * plugin context. * * <p>The instance goes out of scope when scripts are reloaded and * should not be stored. Use {@link #getRegions(Plugin)} if a cached * reference needs to be held.</p> * * @param plugin The plugin context. */ public InternalScriptRegionManager getRegionsDirect(Plugin plugin) { PreCon.notNull(plugin); if (plugin.equals(Nucleus.getPlugin())) return getRegionsDirect(); if (_regionManagers == null) _regionManagers = new HashMap<>(10); InternalScriptRegionManager manager = _regionManagers.get(plugin); if (manager == null) { IDataNode dataNode = DataStorage.get( Nucleus.getPlugin(), new DataPath("nucleus.script-regions")); dataNode.load(); manager = new InternalScriptRegionManager(dataNode); _regionManagers.put(plugin, manager); } return manager; } /** * Load scripts from script folder. * * <p>Clears current scripts and evaluated scripts before loading.</p> * * <p>Loaded scripts are not automatically re-evaluated.</p> */ public void loadScripts() { clearScripts(); clearEvaluated(); if (!_scriptFolder.exists()) return; List<IScript> scripts = ScriptUtils.loadScripts( Nucleus.getPlugin(), getEngineManager(), _scriptFolder, _includeFolder, DirectoryTraversal.RECURSIVE, getScriptFactory()); for (IScript script : scripts) { addScript(script); } } /** * Evaluates all scripts. * * <p>If a script is already evaluated, it is disposed and re-evaluated.</p> */ public void evaluate() { for (IScript script : _scripts.values()) { evaluate(script); } } /** * Evaluates a script. * * <p>Ensures the <em>evaluated</em> script is stored by the manager.</p> */ public boolean evaluate(IScript script) { IEvaluatedScript current = _evaluated.remove(script.getName().toLowerCase()); if (current != null) current.dispose(); IEvaluatedScript evaluated; try { evaluated = script.evaluate(_api); if (evaluated == null) return false; } catch (Exception e) { e.printStackTrace(); return false; } _evaluated.put(script.getName().toLowerCase(), evaluated); return true; } public boolean reload(String scriptName) { PreCon.notNullOrEmpty(scriptName); IScript script = getScript(scriptName); if (script == null) { script = ScriptUtils.loadScript(Nucleus.getPlugin(), _scriptFolder, new File(_scriptFolder, scriptName + ".js"), getScriptFactory()); } else if (script.getFile() != null) { script = ScriptUtils.loadScript(Nucleus.getPlugin(), _scriptFolder, script.getFile(), getScriptFactory()); } if (script == null) return false; _scripts.put(script.getName().toLowerCase(), script); evaluate(script); return true; } public boolean unload(String scriptName) { PreCon.notNullOrEmpty(scriptName); IScript script = getScript(scriptName); if (script == null) return false; _scripts.remove(script.getName().toLowerCase()); IEvaluatedScript evaluated = _evaluated.remove(script.getName().toLowerCase()); if (evaluated != null) evaluated.dispose(); return true; } @Override public void reload() { loadScripts(); GlobalMeta.reset(); ((InternalScriptEngineManager)Nucleus.getScriptEngineManager()).reload(); evaluate(); Scheduler.runTaskLater(Nucleus.getPlugin(), 20, new ScriptReloadGC()); } @Override public boolean addScript(IScript script) { PreCon.notNull(script); _scripts.put(script.getName().toLowerCase(), script); return true; } @Override public boolean removeScript(IScript script) { return removeScript(script.getName()); } @Override public boolean removeScript(String scriptName) { PreCon.notNullOrEmpty(scriptName); if (_scripts.remove(scriptName.toLowerCase()) != null) { IEvaluatedScript evaluated = _evaluated.remove(scriptName.toLowerCase()); if (evaluated != null) { evaluated.dispose(); } return true; } return false; } @Override @Nullable public IScript getScript(String scriptName) { PreCon.notNullOrEmpty(scriptName); return _scripts.get(scriptName.toLowerCase()); } @Override @Nullable public IEvaluatedScript getEvaluated(String scriptName) { PreCon.notNullOrEmpty(scriptName); return _evaluated.get(scriptName.toLowerCase()); } @Override public List<String> getScriptNames() { return new ArrayList<String>(_scripts.keySet()); } @Override public List<IScript> getScripts() { return new ArrayList<>(_scripts.values()); } @Override public List<IEvaluatedScript> getEvaluated() { return new ArrayList<>(_evaluated.values()); } @Override public void clearScripts() { _scripts.clear(); clearEvaluated(); } @Override public IScriptFactory getScriptFactory() { return _scriptFactory; } /* * Clear all evaluated scripts. */ private void clearEvaluated() { for (IEvaluatedScript evaluated : _evaluated.values()) { try { evaluated.dispose(); } catch (Throwable e) { e.printStackTrace(); } } _evaluated.clear(); } private void loadDefaultApi() { Plugin plugin = Nucleus.getPlugin(); _api.add(new SimpleScriptApi(plugin, "econ", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Economy(); } })); _api.add(new SimpleScriptApi(plugin, "events", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Events(plugin); } })); _api.add(new SimpleScriptApi(plugin, "inventory", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Inventory(); } })); _api.add(new SimpleScriptApi(plugin, "itemBank", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_ItemBank(); } })); _api.add(new SimpleScriptApi(plugin, "items", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Items(); } })); _api.add(new SimpleScriptApi(plugin, "jails", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Jails(); } })); _api.add(new SimpleScriptApi(plugin, "locations", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Locations(); } })); _api.add(new SimpleScriptApi(plugin, "msg", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { IMessenger messenger = Nucleus.getMessengerFactory().getAnon(plugin); return new SAPI_Msg(messenger); } })); _api.add(new SimpleScriptApi(plugin, "permissions", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Permissions(); } })); _api.add(new SimpleScriptApi(plugin, "sounds", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Sounds(plugin); } })); _api.add(new SimpleScriptApi(plugin, "depends", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Depends(); } })); _api.add(new SimpleScriptApi(plugin, "scheduler", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Scheduler(plugin); } })); _api.add(new SimpleScriptApi(plugin, "scoreboards", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Scoreboard(); } })); _api.add(new SimpleScriptApi(plugin, "include", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Include(plugin, script, InternalScriptManager.this); } })); _api.add(new SimpleScriptApi(plugin, "titles", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Titles(); } })); _api.add(new SimpleScriptApi(plugin, "actionBars", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_ActionBar(); } })); _api.add(new SimpleScriptApi(plugin, "npcProvider", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_NpcProvider(plugin); } })); _api.add(new SimpleScriptApi(plugin, "views", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Views(plugin); } })); _api.add(new SimpleScriptApi(plugin, "regions", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_Regions(); } })); _api.add(new SimpleScriptApi(plugin, "respacks", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new SAPI_ResourcePacks(); } })); _api.add(new SimpleScriptApi(plugin, "gmeta", new IApiObjectCreator() { @Override public IDisposable create(Plugin plugin, IEvaluatedScript script) { return new GlobalMeta(); } })); } private static class ScriptReloadGC implements Runnable { @Override public void run() { // remove long lived objects System.gc(); } } }