From 4df1abce3fbcca277786c2d68777016b9ca24e0b Mon Sep 17 00:00:00 2001 From: MobiusDev <8391001+MobiusDevelopment@users.noreply.github.com> Date: Fri, 21 Oct 2016 13:26:36 +0000 Subject: [PATCH] Implementing Chris's scripting engine and dropping the old one. Author: UnAfraid Source: L2jUnity free release. --- .../dist/game/config/ScriptEngines.ini | 39 + .../conquerablehalls/flagwar/FlagWar.java | 13 +- .../admincommandhandlers/AdminQuest.java | 182 ++--- .../admincommandhandlers/AdminReload.java | 28 +- .../telnethandlers/ReloadHandler.java | 30 +- .../instances/ChambersOfDelusion/Chamber.java | 2 +- .../com/l2jmobius/gameserver/GameServer.java | 13 +- .../gameserver/handler/EffectHandler.java | 3 +- .../instancemanager/QuestManager.java | 17 +- .../model/events/AbstractScript.java | 46 +- .../gameserver/model/quest/Quest.java | 16 +- .../gameserver/script/Expression.java | 76 -- .../scripting/AbstractExecutionContext.java | 75 ++ .../scripting/AbstractScriptingEngine.java | 74 ++ .../scripting/IExecutionContext.java | 71 ++ .../scripting/IScriptingEngine.java | 75 ++ .../scripting/L2ScriptEngineManager.java | 670 ++++++++---------- .../gameserver/scripting/ManagedScript.java | 97 +++ .../JavaCompilerException.java} | 29 +- .../scripting/java/JavaExecutionContext.java | 239 +++++++ .../scripting/java/JavaScriptingEngine.java | 138 ++++ .../scripting/java/ScriptingClassLoader.java | 55 ++ .../scripting/java/ScriptingFileManager.java | 185 +++++ .../java/ScriptingOutputFileObject.java | 147 ++++ .../taskmanager/tasks/TaskScript.java | 26 +- 25 files changed, 1634 insertions(+), 712 deletions(-) create mode 100644 L2J_Mobius_Test/dist/game/config/ScriptEngines.ini delete mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/script/Expression.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractExecutionContext.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractScriptingEngine.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IExecutionContext.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IScriptingEngine.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ManagedScript.java rename L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/{ScriptManager.java => java/JavaCompilerException.java} (56%) create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaExecutionContext.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaScriptingEngine.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingClassLoader.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingFileManager.java create mode 100644 L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingOutputFileObject.java diff --git a/L2J_Mobius_Test/dist/game/config/ScriptEngines.ini b/L2J_Mobius_Test/dist/game/config/ScriptEngines.ini new file mode 100644 index 0000000000..98284c5b95 --- /dev/null +++ b/L2J_Mobius_Test/dist/game/config/ScriptEngines.ini @@ -0,0 +1,39 @@ +############################################################################### +# Properties retrived with System.getProperty(String) can be used as values +# by enclosing the property name with %. Eg.: %java.class.path% +# +# You can set a property for a scripting engine by language name. +# +# Examples: +# language.Java.source=1.8 +############################################################################### + + +####################################### +# L2J_JavaEngine ##################### +####################################### + +# The prefered java compiler api to use. +# The value is a fully qualified name of a class which implements the javax.toold.JavaCompiler and has a zero argument constructor. +# When the prefered compiler is not set, the first found compiler is used. +# When the prefered compiler is not found, the last found compiler is used. +language.Java.preferedCompiler=com.sun.tools.javac.api.JavacTool +language.Java.preferedCompiler=org.eclipse.jdt.internal.compiler.tool.EclipseCompiler + +# The parent class loader for isolated script class loaders. +# When this property is not specified, has an invalid value or is a class name which could not be found, the System classloader is used. +# Values: System, ThreadContext or a fully qualified java class name +language.Java.classloader=System +#language.Java.classloader=ThreadContext + +# Source compatibility +language.Java.source=1.8 + +# The java sourcepath, when you have a different datapack root, you must change this too. +language.Java.sourcepath=data/scripts + +# The java classpath +language.Java.cp=%java.class.path% + +# The debug informations to generate for compiled class files +language.Java.g=source,lines,vars \ No newline at end of file diff --git a/L2J_Mobius_Test/dist/game/data/scripts/conquerablehalls/flagwar/FlagWar.java b/L2J_Mobius_Test/dist/game/data/scripts/conquerablehalls/flagwar/FlagWar.java index 791065dc24..2719c3b9f4 100644 --- a/L2J_Mobius_Test/dist/game/data/scripts/conquerablehalls/flagwar/FlagWar.java +++ b/L2J_Mobius_Test/dist/game/data/scripts/conquerablehalls/flagwar/FlagWar.java @@ -40,6 +40,7 @@ import com.l2jmobius.gameserver.model.Location; import com.l2jmobius.gameserver.model.TeleportWhereType; import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; +import com.l2jmobius.gameserver.model.entity.Siegable; import com.l2jmobius.gameserver.model.entity.clanhall.ClanHallSiegeEngine; import com.l2jmobius.gameserver.model.entity.clanhall.SiegeStatus; import com.l2jmobius.gameserver.model.zone.type.L2ResidenceHallTeleportZone; @@ -498,17 +499,20 @@ public abstract class FlagWar extends ClanHallSiegeEngine } // Schedule open doors closement and siege start in 2 minutes - ThreadPoolManager.getInstance().scheduleGeneral(new CloseOutterDoorsTask(), 300000); + ThreadPoolManager.getInstance().scheduleGeneral(new CloseOutterDoorsTask(this), 300000); } /** * Runnable class to schedule doors closing and siege start. * @author Zoey76 */ - private class CloseOutterDoorsTask implements Runnable + protected class CloseOutterDoorsTask implements Runnable { - public CloseOutterDoorsTask() + private final Siegable _siegable; + + protected CloseOutterDoorsTask(Siegable clanHallSiege) { + _siegable = clanHallSiege; } @Override @@ -520,7 +524,8 @@ public abstract class FlagWar extends ClanHallSiegeEngine } _hall.getZone().banishNonSiegeParticipants(); - _hall.getSiege().startSiege(); + + _siegable.startSiege(); } } diff --git a/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminQuest.java b/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminQuest.java index 636214355b..d9cc6228fe 100644 --- a/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminQuest.java +++ b/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminQuest.java @@ -16,12 +16,13 @@ */ package handlers.admincommandhandlers; -import java.io.File; +import java.nio.file.Paths; import java.util.List; import java.util.Set; +import java.util.StringTokenizer; import java.util.TreeSet; - -import javax.script.ScriptException; +import java.util.logging.Level; +import java.util.logging.Logger; import com.l2jmobius.gameserver.handler.IAdminCommandHandler; import com.l2jmobius.gameserver.instancemanager.QuestManager; @@ -38,6 +39,7 @@ import com.l2jmobius.gameserver.util.Util; public class AdminQuest implements IAdminCommandHandler { + public static final Logger LOGGER = Logger.getLogger(AdminQuest.class.getName()); private static final String[] ADMIN_COMMANDS = { "admin_quest_reload", @@ -47,137 +49,89 @@ public class AdminQuest implements IAdminCommandHandler "admin_quest_info" }; + private Quest findScript(String script) + { + if (Util.isDigit(script)) + { + return QuestManager.getInstance().getQuest(Integer.parseInt(script)); + } + return QuestManager.getInstance().getQuest(script); + } + @Override public boolean useAdminCommand(String command, L2PcInstance activeChar) { - if (activeChar == null) - { - return false; - } - - // syntax will either be: - // //quest_reload - // //quest_reload - // The questName MUST start with a non-numeric character for this to work, - // regardless which of the two formats is used. - // Example: //quest_reload orc_occupation_change_1 - // Example: //quest_reload chests - // Example: //quest_reload SagasSuperclass - // Example: //quest_reload 12 if (command.startsWith("admin_quest_reload")) { - final String[] parts = command.split(" "); - if (parts.length < 2) + StringTokenizer st = new StringTokenizer(command); + st.nextToken(); // skip command token + + if (!st.hasMoreTokens()) { - activeChar.sendMessage("Usage: //quest_reload ..questName> or //quest_reload "); + activeChar.sendMessage("Usage: //quest_reload or "); + return false; } - else + + String script = st.nextToken(); + Quest quest = findScript(script); + if (quest == null) { - // try the first param as id - try - { - if (QuestManager.getInstance().reload(Integer.parseInt(parts[1]))) - { - activeChar.sendMessage("Quest Reloaded Successfully."); - } - else - { - activeChar.sendMessage("Quest Reloaded Failed"); - } - } - catch (NumberFormatException e) - { - if (QuestManager.getInstance().reload(parts[1])) - { - activeChar.sendMessage("Quest Reloaded Successfully."); - } - else - { - activeChar.sendMessage("Quest Reloaded Failed"); - } - } + activeChar.sendMessage("The script " + script + " couldn't be found!"); + return false; } + + if (!quest.reload()) + { + activeChar.sendMessage("Failed to reload " + script + "!"); + return false; + } + + activeChar.sendMessage("Script successful reloaded."); } - // script load should NOT be used in place of reload. If a script is already loaded - // successfully, quest_reload ought to be used. The script_load command should only - // be used for scripts that failed to load altogether (eg. due to errors) or that - // did not at all exist during server boot. Using script_load to re-load a previously - // loaded script may cause unpredictable script flow, minor loss of data, and more. - // This provides a way to load new scripts without having to reboot the server. else if (command.startsWith("admin_script_load")) { - final String[] parts = command.split(" "); - if (parts.length < 2) + StringTokenizer st = new StringTokenizer(command); + st.nextToken(); // skip command token + + if (!st.hasMoreTokens()) { - // activeChar.sendMessage("Example: //script_load //. "); - activeChar.sendMessage("Example: //script_load quests/SagasSuperclass/__init__.py"); + activeChar.sendMessage("Usage: //script_load path/to/script.java"); + return false; } - else + + String script = st.nextToken(); + try { - File file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, parts[1]); - // Trying to reload by script name. - if (!file.exists()) - { - final Quest quest = QuestManager.getInstance().getQuest(parts[1]); - if (quest != null) - { - file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, quest.getClass().getName().replaceAll("\\.", "/") + ".java"); - } - } - - // Reloading by full path - if (file.isFile()) - { - try - { - L2ScriptEngineManager.getInstance().executeScript(file); - - // This part should be called only when the script is successfully loaded. - activeChar.sendMessage("Script Successfully Loaded."); - } - catch (ScriptException e) - { - activeChar.sendMessage("Failed loading: " + parts[1]); - L2ScriptEngineManager.getInstance().reportScriptFileError(file, e); - } - catch (Exception e) - { - activeChar.sendMessage("Failed loading: " + parts[1]); - } - } - else - { - activeChar.sendMessage("File Not Found: " + parts[1]); - } + L2ScriptEngineManager.getInstance().executeScript(Paths.get(script)); + activeChar.sendMessage("Script loaded seccessful!"); + } + catch (Exception e) + { + activeChar.sendMessage("Failed to load script!"); + LOGGER.log(Level.WARNING, "Failed to load script " + script + "!", e); } } else if (command.startsWith("admin_script_unload")) { - final String[] parts = command.split(" "); - if (parts.length < 2) + StringTokenizer st = new StringTokenizer(command); + st.nextToken(); // skip command token + + if (!st.hasMoreTokens()) { - activeChar.sendMessage("Example: //script_unload questName/questId"); + activeChar.sendMessage("Usage: //script_load path/to/script.java"); + return false; } - else + + String script = st.nextToken(); + Quest quest = findScript(script); + if (quest == null) { - final Quest q = Util.isDigit(parts[1]) ? QuestManager.getInstance().getQuest(Integer.parseInt(parts[1])) : QuestManager.getInstance().getQuest(parts[1]); - - if (q != null) - { - if (q.unload()) - { - activeChar.sendMessage("Script Successfully Unloaded [" + q.getName() + "/" + q.getId() + "]"); - } - else - { - activeChar.sendMessage("Failed unloading [" + q.getName() + "/" + q.getId() + "]."); - } - } - else - { - activeChar.sendMessage("The quest [" + parts[1] + "] was not found!."); - } + activeChar.sendMessage("The script " + script + " couldn't be found!"); + return false; } + + quest.unload(); + activeChar.sendMessage("Script successful unloaded!"); } else if (command.startsWith("admin_show_quests")) { @@ -212,7 +166,7 @@ public class AdminQuest implements IAdminCommandHandler } final NpcHtmlMessage msg = new NpcHtmlMessage(0, 1); - msg.setFile(activeChar.getHtmlPrefix(), "html/admin/npc-quests.htm"); + msg.setFile(activeChar.getHtmlPrefix(), "data/html/admin/npc-quests.htm"); msg.replace("%quests%", sb.toString()); msg.replace("%objid%", character.getObjectId()); msg.replace("%questName%", ""); @@ -318,7 +272,7 @@ public class AdminQuest implements IAdminCommandHandler } final NpcHtmlMessage msg = new NpcHtmlMessage(0, 1); - msg.setFile(activeChar.getHtmlPrefix(), "html/admin/npc-quests.htm"); + msg.setFile(activeChar.getHtmlPrefix(), "data/html/admin/npc-quests.htm"); msg.replace("%quests%", sb.toString()); msg.replace("%questName%", "
Reload " + quest.getName() + " Unload
"); activeChar.sendPacket(msg); @@ -331,4 +285,4 @@ public class AdminQuest implements IAdminCommandHandler { return ADMIN_COMMANDS; } -} +} \ No newline at end of file diff --git a/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminReload.java b/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminReload.java index 51e8e2e5c0..6ec4f935ec 100644 --- a/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminReload.java +++ b/L2J_Mobius_Test/dist/game/data/scripts/handlers/admincommandhandlers/AdminReload.java @@ -18,8 +18,8 @@ package handlers.admincommandhandlers; import java.io.File; import java.util.StringTokenizer; - -import javax.script.ScriptException; +import java.util.logging.Level; +import java.util.logging.Logger; import com.l2jmobius.Config; import com.l2jmobius.gameserver.cache.HtmCache; @@ -55,6 +55,8 @@ import com.l2jmobius.gameserver.util.Util; */ public class AdminReload implements IAdminCommandHandler { + private static final Logger LOGGER = Logger.getLogger(AdminReload.class.getName()); + private static final String[] ADMIN_COMMANDS = { "admin_reload" @@ -217,31 +219,29 @@ public class AdminReload implements IAdminCommandHandler } case "effect": { - final File file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, "handlers/EffectMasterHandler.java"); try { - L2ScriptEngineManager.getInstance().executeScript(file); - AdminData.getInstance().broadcastMessageToGMs(activeChar.getName() + ": Reloaded Effects."); + L2ScriptEngineManager.getInstance().executeEffectMasterHandler(); + AdminData.getInstance().broadcastMessageToGMs(activeChar.getName() + ": Reloaded effect master handler."); } - catch (ScriptException e) + catch (Exception e) { - L2ScriptEngineManager.getInstance().reportScriptFileError(file, e); - activeChar.sendMessage("There was an error while loading handlers."); + LOGGER.log(Level.WARNING, "Failed executing effect master handler!", e); + activeChar.sendMessage("Error reloading effect master handler!"); } break; } case "handler": { - final File file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, "handlers/MasterHandler.java"); try { - L2ScriptEngineManager.getInstance().executeScript(file); - AdminData.getInstance().broadcastMessageToGMs(activeChar.getName() + ": Reloaded Handlers."); + L2ScriptEngineManager.getInstance().executeMasterHandler(); + AdminData.getInstance().broadcastMessageToGMs(activeChar.getName() + ": Reloaded master handler."); } - catch (ScriptException e) + catch (Exception e) { - L2ScriptEngineManager.getInstance().reportScriptFileError(file, e); - activeChar.sendMessage("There was an error while loading handlers."); + LOGGER.log(Level.WARNING, "Failed executing master handler!", e); + activeChar.sendMessage("Error reloading master handler!"); } break; } diff --git a/L2J_Mobius_Test/dist/game/data/scripts/handlers/telnethandlers/ReloadHandler.java b/L2J_Mobius_Test/dist/game/data/scripts/handlers/telnethandlers/ReloadHandler.java index d26aa63de6..c5698ecbc7 100644 --- a/L2J_Mobius_Test/dist/game/data/scripts/handlers/telnethandlers/ReloadHandler.java +++ b/L2J_Mobius_Test/dist/game/data/scripts/handlers/telnethandlers/ReloadHandler.java @@ -16,12 +16,11 @@ */ package handlers.telnethandlers; -import java.io.File; import java.io.PrintWriter; import java.net.Socket; +import java.nio.file.Paths; import java.util.StringTokenizer; - -import javax.script.ScriptException; +import java.util.logging.Level; import com.l2jmobius.gameserver.cache.HtmCache; import com.l2jmobius.gameserver.data.sql.impl.TeleportLocationTable; @@ -116,29 +115,16 @@ public class ReloadHandler implements ITelnetHandler { try { - final String questPath = st.hasMoreTokens() ? st.nextToken() : ""; + String questPath = st.hasMoreTokens() ? st.nextToken() : ""; - final File file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, questPath); - if (file.isFile()) + try { - try - { - L2ScriptEngineManager.getInstance().executeScript(file); - _print.println(file.getName() + " was successfully loaded!\n"); - } - catch (ScriptException e) - { - _print.println("Failed loading: " + questPath); - L2ScriptEngineManager.getInstance().reportScriptFileError(file, e); - } - catch (Exception e) - { - _print.println("Failed loading: " + questPath); - } + L2ScriptEngineManager.getInstance().executeScript(Paths.get(questPath)); + _print.println(questPath + " was successfully loaded!\n"); } - else + catch (Exception e) { - _print.println(file.getName() + " is not a file in: " + questPath); + _log.log(Level.WARNING, "Failed to execute script!", e); } } catch (StringIndexOutOfBoundsException e) diff --git a/L2J_Mobius_Test/dist/game/data/scripts/instances/ChambersOfDelusion/Chamber.java b/L2J_Mobius_Test/dist/game/data/scripts/instances/ChambersOfDelusion/Chamber.java index dd18f1f7f0..ff5d24b244 100644 --- a/L2J_Mobius_Test/dist/game/data/scripts/instances/ChambersOfDelusion/Chamber.java +++ b/L2J_Mobius_Test/dist/game/data/scripts/instances/ChambersOfDelusion/Chamber.java @@ -117,7 +117,7 @@ abstract class Chamber extends AbstractInstance } } - class ChangeRoomTask implements Runnable + protected class ChangeRoomTask implements Runnable { @Override public void run() diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/GameServer.java index 5877c0b5dc..aeeedc2d9b 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/GameServer.java @@ -184,7 +184,7 @@ public final class GameServer new File("log/game").mkdirs(); // load script engines - printSection("Engines"); + printSection("Scripting Engines"); L2ScriptEngineManager.getInstance(); printSection("World"); @@ -329,15 +329,12 @@ public final class GameServer try { - _log.info(getClass().getSimpleName() + ": Loading server scripts:"); - if (!Config.ALT_DEV_NO_HANDLERS || !Config.ALT_DEV_NO_QUESTS) - { - L2ScriptEngineManager.getInstance().executeScriptList(new File(Config.DATAPACK_ROOT, "scripts.cfg")); - } + _log.info("Loading server scripts..."); + L2ScriptEngineManager.getInstance().executeScriptList(); } - catch (IOException ioe) + catch (Exception e) { - _log.severe(getClass().getSimpleName() + ": Failed loading scripts.cfg, scripts are not going to be loaded!"); + _log.log(Level.WARNING, "Failed to execute script list!", e); } SpawnTable.getInstance().load(); diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/handler/EffectHandler.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/handler/EffectHandler.java index d770e06630..5e089781a9 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/handler/EffectHandler.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/handler/EffectHandler.java @@ -16,7 +16,6 @@ */ package com.l2jmobius.gameserver.handler; -import java.io.File; import java.util.HashMap; import java.util.Map; @@ -63,7 +62,7 @@ public final class EffectHandler implements IHandler +public final class QuestManager { protected static final Logger _log = Logger.getLogger(QuestManager.class.getName()); @@ -93,9 +90,9 @@ public final class QuestManager extends ScriptManager try { - L2ScriptEngineManager.getInstance().executeScriptList(new File(Config.DATAPACK_ROOT, "scripts.cfg")); + L2ScriptEngineManager.getInstance().executeScriptList(); } - catch (IOException e) + catch (Exception e) { _log.log(Level.SEVERE, getClass().getSimpleName() + ": Failed loading scripts.cfg, no script going to be loaded!", e); } @@ -216,24 +213,16 @@ public final class QuestManager extends ScriptManager return _quests; } - @Override public boolean unload(Quest ms) { ms.saveGlobalData(); return removeScript(ms); } - @Override - public String getScriptManagerName() - { - return getClass().getSimpleName(); - } - /** * Gets all the registered scripts. * @return all the scripts */ - @Override public Map getScripts() { return _scripts; diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/events/AbstractScript.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/events/AbstractScript.java index 4e9bbfe022..bd9bca33d5 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/events/AbstractScript.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/events/AbstractScript.java @@ -16,7 +16,6 @@ */ package com.l2jmobius.gameserver.model.events; -import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; @@ -33,8 +32,6 @@ import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; -import javax.script.ScriptException; - import com.l2jmobius.Config; import com.l2jmobius.gameserver.GameTimeController; import com.l2jmobius.gameserver.ai.CtrlIntention; @@ -114,7 +111,6 @@ import com.l2jmobius.gameserver.model.events.returns.AbstractEventReturn; import com.l2jmobius.gameserver.model.events.returns.TerminateReturn; import com.l2jmobius.gameserver.model.holders.ItemHolder; import com.l2jmobius.gameserver.model.holders.SkillHolder; -import com.l2jmobius.gameserver.model.interfaces.INamable; import com.l2jmobius.gameserver.model.interfaces.IPositionable; import com.l2jmobius.gameserver.model.itemcontainer.Inventory; import com.l2jmobius.gameserver.model.itemcontainer.PcInventory; @@ -133,26 +129,21 @@ import com.l2jmobius.gameserver.network.serverpackets.ExUserInfoInvenWeight; import com.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; import com.l2jmobius.gameserver.network.serverpackets.SpecialCamera; import com.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import com.l2jmobius.gameserver.scripting.L2ScriptEngineManager; -import com.l2jmobius.gameserver.scripting.ScriptManager; +import com.l2jmobius.gameserver.scripting.ManagedScript; import com.l2jmobius.gameserver.util.MinionList; import com.l2jmobius.util.Rnd; /** - * Abstract script. - * @author KenM, UnAfraid, Zoey76 + * @author UnAfraid */ -public abstract class AbstractScript implements INamable +public abstract class AbstractScript extends ManagedScript { public static final Logger _log = Logger.getLogger(AbstractScript.class.getName()); private final Map> _registeredIds = new ConcurrentHashMap<>(); private final List _listeners = new CopyOnWriteArrayList<>(); - private final File _scriptFile; - private boolean _isActive; public AbstractScript() { - _scriptFile = L2ScriptEngineManager.getInstance().getCurrentLoadingScript(); initializeAnnotationListeners(); } @@ -298,38 +289,11 @@ public abstract class AbstractScript implements INamable } } - public void setActive(boolean status) - { - _isActive = status; - } - - public boolean isActive() - { - return _isActive; - } - - public File getScriptFile() - { - return _scriptFile; - } - - public boolean reload() - { - try - { - L2ScriptEngineManager.getInstance().executeScript(getScriptFile()); - return true; - } - catch (ScriptException e) - { - return false; - } - } - /** * Unloads all listeners registered by this class. * @return {@code true} */ + @Override public boolean unload() { _listeners.forEach(AbstractEventListener::unregisterMe); @@ -337,8 +301,6 @@ public abstract class AbstractScript implements INamable return true; } - public abstract ScriptManager getManager(); - // --------------------------------------------------------------------------------------------------------------------------- /** diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/quest/Quest.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/quest/Quest.java index e4991c3510..61df2357c0 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/quest/Quest.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/model/quest/Quest.java @@ -69,7 +69,6 @@ import com.l2jmobius.gameserver.network.serverpackets.ActionFailed; import com.l2jmobius.gameserver.network.serverpackets.ExQuestNpcLogList; import com.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; import com.l2jmobius.gameserver.network.serverpackets.NpcQuestHtmlMessage; -import com.l2jmobius.gameserver.scripting.ScriptManager; import com.l2jmobius.util.Rnd; import com.l2jmobius.util.Util; @@ -209,7 +208,6 @@ public class Quest extends AbstractScript implements IIdentifiable return _initialState; } - @Override public String getName() { return _name; @@ -1431,7 +1429,7 @@ public class Quest extends AbstractScript implements IIdentifiable */ public boolean showError(L2PcInstance player, Throwable t) { - _log.log(Level.WARNING, getScriptFile().getAbsolutePath(), t); + _log.log(Level.WARNING, getScriptFile().toAbsolutePath().toString(), t); if (t.getMessage() == null) { _log.warning(getClass().getSimpleName() + ": " + t.getMessage()); @@ -2650,6 +2648,12 @@ public class Quest extends AbstractScript implements IIdentifiable takeItems(player, -1, questItemIds); } + @Override + public String getScriptName() + { + return getName(); + } + @Override public void setActive(boolean status) { @@ -2707,12 +2711,6 @@ public class Quest extends AbstractScript implements IIdentifiable return super.unload(); } - @Override - public ScriptManager getManager() - { - return QuestManager.getInstance(); - } - public void setOnEnterWorld(boolean state) { if (state) diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/script/Expression.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/script/Expression.java deleted file mode 100644 index ce89b9eb74..0000000000 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/script/Expression.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.l2jmobius.gameserver.script; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.script.ScriptContext; - -public class Expression -{ - protected static final Logger _log = Logger.getLogger(Expression.class.getName()); - private final ScriptContext _context; - @SuppressWarnings("unused") - private final String _lang; - @SuppressWarnings("unused") - private final String _code; - - public static Expression create(ScriptContext context, String lang, String code) - { - try - { - return new Expression(context, lang, code); - } - catch (Exception e) - { - _log.log(Level.WARNING, "", e); - return null; - } - } - - private Expression(ScriptContext pContext, String pLang, String pCode) - { - _context = pContext; - _lang = pLang; - _code = pCode; - } - - public void addDynamicVariable(String name, T value) - { - try - { - _context.setAttribute(name, value, ScriptContext.ENGINE_SCOPE); - } - catch (Exception e) - { - _log.log(Level.WARNING, "", e); - } - } - - public void removeDynamicVariable(String name) - { - try - { - _context.removeAttribute(name, ScriptContext.ENGINE_SCOPE); - } - catch (Exception e) - { - _log.log(Level.WARNING, "", e); - } - } -} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractExecutionContext.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractExecutionContext.java new file mode 100644 index 0000000000..8b8fa93a47 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractExecutionContext.java @@ -0,0 +1,75 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +/** + * @author HorridoJoho + * @param + */ +public abstract class AbstractExecutionContext implements IExecutionContext +{ + private final T _engine; + private final Map _properties; + private volatile Path _currentExecutingScipt; + + protected AbstractExecutionContext(final T engine) + { + if (engine == null) + { + throw new IllegalArgumentException(); + } + _engine = engine; + _properties = new HashMap<>(); + } + + protected final void setCurrentExecutingScript(final Path currentExecutingScript) + { + _currentExecutingScipt = currentExecutingScript; + } + + @Override + public final String setProperty(final String key, final String value) + { + return _properties.put(key, value); + } + + @Override + public final String getProperty(final String key) + { + if (!_properties.containsKey(key)) + { + return _engine.getProperty(key); + } + return _properties.get(key); + } + + @Override + public final Path getCurrentExecutingScript() + { + return _currentExecutingScipt; + } + + @Override + public final T getScriptingEngine() + { + return _engine; + } +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractScriptingEngine.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractScriptingEngine.java new file mode 100644 index 0000000000..d98234cc36 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/AbstractScriptingEngine.java @@ -0,0 +1,74 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author HorridoJoho + */ +public abstract class AbstractScriptingEngine implements IScriptingEngine +{ + private final String _engineName; + private final String _engineVersion; + private final String[] _commonFileExtensions; + private final Map _properties; + + protected AbstractScriptingEngine(final String engineName, final String engineVersion, final String... commonFileExtensions) + { + if ((engineName == null) || engineName.isEmpty() || (engineVersion == null) || engineVersion.isEmpty() || (commonFileExtensions == null) || (commonFileExtensions.length == 0)) + { + throw new IllegalArgumentException(); + } + _engineName = engineName; + _engineVersion = engineVersion; + _commonFileExtensions = commonFileExtensions; + _properties = new HashMap<>(); + } + + @Override + public final String setProperty(final String key, final String value) + { + return _properties.put(key, value); + } + + @Override + public final String getProperty(final String key) + { + return _properties.get(key); + } + + @Override + public final String getEngineName() + { + return _engineName; + } + + @Override + public final String getEngineVersion() + { + return _engineVersion; + } + + @Override + public final String[] getCommonFileExtensions() + { + return Arrays.copyOf(_commonFileExtensions, _commonFileExtensions.length); + } +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IExecutionContext.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IExecutionContext.java new file mode 100644 index 0000000000..057ba793e7 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IExecutionContext.java @@ -0,0 +1,71 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Map.Entry; + +/** + * @author HorridoJoho + */ +public interface IExecutionContext +{ + /** + * Properties set here override the settings from the IScriptEngine
+ * this class was created from. + * @param key the key + * @param value the value + * @return the previous value, or null when this key was not present before + */ + String setProperty(String key, String value); + + /** + * Executes all script in the iterable. + * @param sourcePaths the scripts to execute + * @return map of failed executions, Path=source file Throwable=thrown exception + * @throws Exception preparation for script execution failed + */ + Map executeScripts(Iterable sourcePaths) throws Exception; + + /** + * Executes a single file. + * @param sourcePath the script to execute + * @return entry of failed execution, Path=source file Throwable=thrown exception + * @throws Exception preparation for script execution failed + */ + Entry executeScript(Path sourcePath) throws Exception; + + /** + * Method to get the specified property value. + * @param key the key + * @return the value, or null if the key is not present + */ + String getProperty(String key); + + /** + * Method to get the current executing script file. + * @return the currently executing script file, null if non + */ + Path getCurrentExecutingScript(); + + /** + * Method to get the script engine this execution context belongs to. + * @return the script engine this execution context belongs to + */ + IScriptingEngine getScriptingEngine(); +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IScriptingEngine.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IScriptingEngine.java new file mode 100644 index 0000000000..520d8622bb --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/IScriptingEngine.java @@ -0,0 +1,75 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +/** + * @author HorridoJoho + */ +public interface IScriptingEngine +{ + /** + * Sets script engine properties. The script values will be available
+ * to the the insatnces created {@link IExecutionContext} implementation. + * @param key the key + * @param value the value + * @return the previous value, or null when this key was not present before + */ + String setProperty(String key, String value); + + /** + * Creates an execution context. + * @return the created execution context. + */ + IExecutionContext createExecutionContext(); + + /** + * Method to get the specified property value. + * @param key the key + * @return the value,or null if the key is not present + */ + String getProperty(String key); + + /** + * Method to get the engine name. + * @return the engine name + */ + String getEngineName(); + + /** + * Method to get the engine version. + * @return the engine version + */ + String getEngineVersion(); + + /** + * Method to get the scripting language name. + * @return the scripting engine name + */ + String getLanguageName(); + + /** + * Method to get the the language version. + * @return the language version + */ + String getLanguageVersion(); + + /** + * Method to retrive the commonly used file extensions for the language. + * @return the commonly used file extensions for the language + */ + String[] getCommonFileExtensions(); +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/L2ScriptEngineManager.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/L2ScriptEngineManager.java index df80001e6b..a669586dd8 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/L2ScriptEngineManager.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/L2ScriptEngineManager.java @@ -1,363 +1,307 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.l2jmobius.gameserver.scripting; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.script.Compilable; -import javax.script.CompiledScript; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import javax.script.SimpleScriptContext; - -import com.l2jmobius.Config; -import com.l2jmobius.commons.javaengine.JavaScriptEngineFactory; - -/** - * Caches script engines and provides functionality for executing and managing scripts. - * @author KenM - */ -public final class L2ScriptEngineManager -{ - private static final Logger _log = Logger.getLogger(L2ScriptEngineManager.class.getName()); - - public static final File SCRIPT_FOLDER = new File(Config.DATAPACK_ROOT.getAbsolutePath(), "scripts"); - - public static L2ScriptEngineManager getInstance() - { - return SingletonHolder._instance; - } - - private final Map _extEngines = new HashMap<>(); - private final List> _scriptManagers = new LinkedList<>(); - - private File _currentLoadingScript; - - /** - * If the script engine supports compilation the script is compiled before execution.
- */ - private static final boolean ATTEMPT_COMPILATION = true; - - protected L2ScriptEngineManager() - { - final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); - final ScriptEngineFactory factory = new JavaScriptEngineFactory(); - scriptEngineManager.registerEngineExtension("java", factory); - _extEngines.put("java", factory.getScriptEngine()); - _log.info("Script Engine: " + factory.getEngineName() + " " + factory.getEngineVersion() + " - Language: " + factory.getLanguageName() + " - Language Version: " + factory.getLanguageVersion()); - } - - private ScriptEngine getEngineByExtension(String ext) - { - return _extEngines.get(ext); - } - - public void executeScriptList(File list) throws IOException - { - if (Config.ALT_DEV_NO_QUESTS) - { - if (!Config.ALT_DEV_NO_HANDLERS) - { - try - { - executeScript(new File(SCRIPT_FOLDER, "handlers/MasterHandler.java")); - _log.info("Handlers loaded, all other scripts skipped"); - } - catch (ScriptException se) - { - _log.log(Level.WARNING, "", se); - } - } - return; - } - - if (!list.isFile()) - { - throw new IllegalArgumentException("Argument must be an file containing a list of scripts to be loaded"); - } - - try (FileInputStream fis = new FileInputStream(list); - InputStreamReader isr = new InputStreamReader(fis); - LineNumberReader lnr = new LineNumberReader(isr)) - { - String line; - while ((line = lnr.readLine()) != null) - { - if (Config.ALT_DEV_NO_HANDLERS && line.contains("MasterHandler.java")) - { - continue; - } - - final String[] parts = line.trim().split("#"); - - if ((parts.length > 0) && !parts[0].isEmpty() && (parts[0].charAt(0) != '#')) - { - line = parts[0]; - - if (line.endsWith("/**")) - { - line = line.substring(0, line.length() - 3); - } - else if (line.endsWith("/*")) - { - line = line.substring(0, line.length() - 2); - } - - final File file = new File(SCRIPT_FOLDER, line); - - if (file.isDirectory() && parts[0].endsWith("/**")) - { - executeAllScriptsInDirectory(file, true, 32); - } - else if (file.isDirectory() && parts[0].endsWith("/*")) - { - executeAllScriptsInDirectory(file); - } - else if (file.isFile()) - { - try - { - executeScript(file); - } - catch (ScriptException e) - { - reportScriptFileError(file, e); - } - } - else - { - _log.warning("Failed loading: (" + file.getCanonicalPath() + ") @ " + list.getName() + ":" + lnr.getLineNumber() + " - Reason: doesnt exists or is not a file."); - } - } - } - } - } - - public void executeAllScriptsInDirectory(File dir) - { - executeAllScriptsInDirectory(dir, false, 0); - } - - public void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth) - { - executeAllScriptsInDirectory(dir, recurseDown, maxDepth, 0); - } - - private void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth, int currentDepth) - { - if (!dir.isDirectory()) - { - throw new IllegalArgumentException("The argument directory either doesnt exists or is not an directory."); - } - - final File[] files = dir.listFiles(); - if (files == null) - { - return; - } - - for (File file : files) - { - if (file.isDirectory() && recurseDown && (maxDepth > currentDepth)) - { - executeAllScriptsInDirectory(file, recurseDown, maxDepth, currentDepth + 1); - } - else if (file.isFile()) - { - try - { - final String name = file.getName(); - final int lastIndex = name.lastIndexOf('.'); - String extension; - if (lastIndex != -1) - { - extension = name.substring(lastIndex + 1); - final ScriptEngine engine = getEngineByExtension(extension); - if (engine != null) - { - executeScript(engine, file); - } - } - } - catch (ScriptException e) - { - reportScriptFileError(file, e); - } - } - } - } - - public void executeScript(File file) throws ScriptException - { - final String name = file.getName(); - final int lastIndex = name.lastIndexOf('.'); - String extension; - if (lastIndex == -1) - { - throw new ScriptException("Script file (" + name + ") doesnt has an extension that identifies the ScriptEngine to be used."); - } - extension = name.substring(lastIndex + 1); - final ScriptEngine engine = getEngineByExtension(extension); - if (engine == null) - { - throw new ScriptException("No engine registered for extension (" + extension + ")"); - } - executeScript(engine, file); - } - - public void executeScript(ScriptEngine engine, File file) throws ScriptException - { - { - final String name = file.getAbsolutePath() + ".error.log"; - final File errorLog = new File(name); - if (errorLog.isFile()) - { - errorLog.delete(); - } - } - - final String relativeName = file.getAbsolutePath().substring(SCRIPT_FOLDER.getAbsolutePath().length() + 1).replace('\\', '/'); - try (FileInputStream fis = new FileInputStream(file); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader reader = new BufferedReader(isr)) - { - final ScriptContext context = new SimpleScriptContext(); - context.setAttribute("mainClass", getClassForFile(file).replace('/', '.').replace('\\', '.'), ScriptContext.ENGINE_SCOPE); - context.setAttribute(ScriptEngine.FILENAME, relativeName, ScriptContext.ENGINE_SCOPE); - context.setAttribute("classpath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE); - context.setAttribute("sourcepath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE); - setCurrentLoadingScript(file); - try - { - engine.eval(reader, context); - } - finally - { - setCurrentLoadingScript(null); - engine.getContext().removeAttribute(ScriptEngine.FILENAME, ScriptContext.ENGINE_SCOPE); - engine.getContext().removeAttribute("mainClass", ScriptContext.ENGINE_SCOPE); - } - } - catch (IOException e) - { - _log.log(Level.WARNING, "Error executing script!", e); - } - } - - public static String getClassForFile(File script) - { - final String path = script.getAbsolutePath(); - final String scpPath = SCRIPT_FOLDER.getAbsolutePath(); - return path.startsWith(scpPath) ? path.substring(scpPath.length() + 1, path.lastIndexOf('.')) : null; - } - - public ScriptContext getScriptContext(ScriptEngine engine) - { - return engine.getContext(); - } - - public Object eval(ScriptEngine engine, String script, ScriptContext context) throws ScriptException - { - if (!(engine instanceof Compilable) || !ATTEMPT_COMPILATION) - { - return context != null ? engine.eval(script, context) : engine.eval(script); - } - final Compilable eng = (Compilable) engine; - final CompiledScript cs = eng.compile(script); - return context != null ? cs.eval(context) : cs.eval(); - } - - public Object eval(ScriptEngine engine, String script) throws ScriptException - { - return eval(engine, script, null); - } - - public void reportScriptFileError(File script, ScriptException e) - { - final String dir = script.getParent(); - if (dir != null) - { - final File file = new File(dir + "/" + script.getName() + ".error.log"); - try (FileOutputStream fos = new FileOutputStream(file)) - { - final String errorHeader = "Error on: " + file.getCanonicalPath() + Config.EOL + "Line: " + e.getLineNumber() + " - Column: " + e.getColumnNumber() + Config.EOL + Config.EOL; - fos.write(errorHeader.getBytes()); - fos.write(e.getMessage().getBytes()); - _log.warning("Failed executing script: " + script.getAbsolutePath() + ". See " + file.getName() + " for details."); - } - catch (IOException ioe) - { - _log.log(Level.WARNING, "Failed executing script: " + script.getAbsolutePath() + Config.EOL + e.getMessage() + "Additionally failed when trying to write an error report on script directory. Reason: " + ioe.getMessage(), ioe); - } - } - else - { - _log.log(Level.WARNING, "Failed executing script: " + script.getAbsolutePath() + Config.EOL + e.getMessage() + "Additionally failed when trying to write an error report on script directory.", e); - } - } - - public void registerScriptManager(ScriptManager manager) - { - _scriptManagers.add(manager); - } - - public void removeScriptManager(ScriptManager manager) - { - _scriptManagers.remove(manager); - } - - public List> getScriptManagers() - { - return _scriptManagers; - } - - /** - * @param currentLoadingScript The currentLoadingScript to set. - */ - protected void setCurrentLoadingScript(File currentLoadingScript) - { - _currentLoadingScript = currentLoadingScript; - } - - /** - * @return Returns the currentLoadingScript. - */ - public File getCurrentLoadingScript() - { - return _currentLoadingScript; - } - - private static class SingletonHolder - { - protected static final L2ScriptEngineManager _instance = new L2ScriptEngineManager(); - } -} +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.LineNumberReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.l2jmobius.Config; +import com.l2jmobius.gameserver.scripting.java.JavaScriptingEngine; + +/** + * Caches script engines and provides functionality for executing and managing scripts. + * @author KenM, HorridoJoho + */ +public final class L2ScriptEngineManager +{ + private static final Logger _log = Logger.getLogger(L2ScriptEngineManager.class.getName()); + public static final Path SCRIPT_LIST_FILE = Paths.get(Config.DATAPACK_ROOT.getAbsolutePath(), "data", "scripts.cfg"); + public static final Path SCRIPT_FOLDER = Paths.get(Config.DATAPACK_ROOT.getAbsolutePath(), "data", "scripts"); + public static final Path MASTER_HANDLER_FILE = Paths.get(SCRIPT_FOLDER.toString(), "handlers", "MasterHandler.java"); + public static final Path EFFECT_MASTER_HANDLER_FILE = Paths.get(SCRIPT_FOLDER.toString(), "handlers", "EffectMasterHandler.java"); + + private final Map _extEngines = new HashMap<>(); + private IExecutionContext _currentExecutionContext = null; + + protected L2ScriptEngineManager() + { + final Properties props = loadProperties(); + + // Load external script engines + ServiceLoader.load(IScriptingEngine.class).forEach(engine -> + { + registerEngine(engine, props); + }); + + // No external script engines + if (_extEngines.isEmpty()) + { + registerEngine(new JavaScriptingEngine(), props); + } + } + + private Properties loadProperties() + { + Properties props = null; + try (FileInputStream fis = new FileInputStream("config/ScriptEngines.ini")) + { + props = new Properties(); + props.load(fis); + } + catch (Exception e) + { + props = null; + _log.warning("Couldn't load ScriptEngines.ini: " + e.getMessage()); + } + return props; + } + + private void registerEngine(IScriptingEngine engine, Properties props) + { + maybeSetProperties("language." + engine.getLanguageName() + ".", props, engine); + final IExecutionContext context = engine.createExecutionContext(); + for (String commonExtension : engine.getCommonFileExtensions()) + { + _extEngines.put(commonExtension, context); + } + + _log.info("ScriptEngine: " + engine.getEngineName() + " " + engine.getEngineVersion() + " (" + engine.getLanguageName() + " " + engine.getLanguageVersion() + ")"); + } + + private void maybeSetProperties(String propPrefix, Properties props, IScriptingEngine engine) + { + if (props == null) + { + return; + } + + for (final Entry prop : props.entrySet()) + { + String key = (String) prop.getKey(); + String value = (String) prop.getValue(); + + if (key.startsWith(propPrefix)) + { + key = key.substring(propPrefix.length()); + if (value.startsWith("%") && value.endsWith("%")) + { + value = System.getProperty(value.substring(1, value.length() - 1)); + } + + engine.setProperty(key, value); + } + } + } + + private IExecutionContext getEngineByExtension(String ext) + { + return _extEngines.get(ext); + } + + private String getFileExtension(Path p) + { + final String name = p.getFileName().toString(); + final int lastDotIdx = name.lastIndexOf('.'); + if (lastDotIdx == -1) + { + return null; + } + + final String extension = name.substring(lastDotIdx + 1); + if (extension.isEmpty()) + { + return null; + } + + return extension; + } + + private void checkExistingFile(String messagePre, Path filePath) throws Exception + { + if (!Files.exists(filePath)) + { + throw new Exception(messagePre + ": " + filePath + " does not exists!"); + } + else if (!Files.isRegularFile(filePath)) + { + throw new Exception(messagePre + ": " + filePath + " is not a file!"); + } + } + + public void executeMasterHandler() throws Exception + { + executeScript(MASTER_HANDLER_FILE); + } + + public void executeEffectMasterHandler() throws Exception + { + executeScript(EFFECT_MASTER_HANDLER_FILE); + } + + public void executeScriptList() throws Exception + { + if (Config.ALT_DEV_NO_QUESTS) + { + return; + } + + // throws exception if not exists or not file + checkExistingFile("ScriptList", SCRIPT_LIST_FILE); + + try (final LineNumberReader lnr = new LineNumberReader(new FileReader(SCRIPT_LIST_FILE.toString()))) + { + final LinkedHashMap> files = new LinkedHashMap<>(); + final LinkedList extWithoutEngine = new LinkedList<>(); + + String line; + while ((line = lnr.readLine()) != null) + { + line = line.trim(); + if (line.isEmpty() || (line.charAt(0) == '#')) + { + continue; + } + + Path sourceFile = SCRIPT_FOLDER.resolve(line); + try + { + checkExistingFile("ScriptFile", sourceFile); + } + catch (Exception e) + { + _log.warning(e.getMessage()); + continue; + } + + sourceFile = sourceFile.toAbsolutePath(); + final String ext = getFileExtension(sourceFile); + if (ext == null) + { + _log.warning("ScriptFile: " + sourceFile + " does not have an extension to determine the script engine!"); + continue; + } + + final IExecutionContext engine = getEngineByExtension(ext); + if (engine == null) + { + if (!extWithoutEngine.contains(ext)) + { + extWithoutEngine.add(ext); + _log.warning("ScriptEngine: No engine registered for extension " + ext + "!"); + } + continue; + } + + LinkedList ll = files.get(engine); + if (ll == null) + { + ll = new LinkedList<>(); + files.put(engine, ll); + } + ll.add(sourceFile); + } + + for (Entry> entry : files.entrySet()) + { + _currentExecutionContext = entry.getKey(); + try + { + Map invokationErrors = entry.getKey().executeScripts(entry.getValue()); + for (Entry entry2 : invokationErrors.entrySet()) + { + _log.log(Level.WARNING, "ScriptEngine: " + entry2.getKey() + " failed execution!", entry2.getValue()); + } + } + finally + { + _currentExecutionContext = null; + } + } + } + } + + public void executeScript(Path sourceFile) throws Exception + { + if (sourceFile == null) + { + throw new NullPointerException(); + } + + if (!sourceFile.isAbsolute()) + { + sourceFile = SCRIPT_FOLDER.resolve(sourceFile); + } + + // throws exception if not exists or not file + checkExistingFile("ScriptFile", sourceFile); + + sourceFile = sourceFile.toAbsolutePath(); + String ext = getFileExtension(sourceFile); + if (ext == null) + { + throw new Exception("ScriptFile: " + sourceFile + " does not have an extension to determine the script engine!"); + } + + IExecutionContext engine = getEngineByExtension(ext); + if (engine == null) + { + throw new Exception("ScriptEngine: No engine registered for extension " + ext + "!"); + } + + _currentExecutionContext = engine; + try + { + Entry error = engine.executeScript(sourceFile); + if (error != null) + { + throw new Exception("ScriptEngine: " + error.getKey() + " failed execution!", error.getValue()); + } + } + finally + { + _currentExecutionContext = null; + } + } + + protected Path getCurrentLoadingScript() + { + return _currentExecutionContext.getCurrentExecutingScript(); + } + + public static L2ScriptEngineManager getInstance() + { + return SingletonHolder._instance; + } + + private static class SingletonHolder + { + protected static final L2ScriptEngineManager _instance = new L2ScriptEngineManager(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ManagedScript.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ManagedScript.java new file mode 100644 index 0000000000..c6349de6fb --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ManagedScript.java @@ -0,0 +1,97 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting; + +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Abstract class for classes that are meant to be implemented by scripts.
+ * @author KenM + */ +public abstract class ManagedScript +{ + private static final Logger LOGGER = Logger.getLogger(ManagedScript.class.getName()); + + private final Path _scriptFile; + private long _lastLoadTime; + private boolean _isActive; + + public ManagedScript() + { + _scriptFile = L2ScriptEngineManager.getInstance().getCurrentLoadingScript(); + setLastLoadTime(System.currentTimeMillis()); + } + + /** + * Attempts to reload this script and to refresh the necessary bindings with it ScriptControler.
+ * Subclasses of this class should override this method to properly refresh their bindings when necessary. + * @return true if and only if the script was reloaded, false otherwise. + */ + public boolean reload() + { + try + { + L2ScriptEngineManager.getInstance().executeScript(getScriptFile()); + return true; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to reload script!", e); + return false; + } + } + + public abstract boolean unload(); + + public void setActive(boolean status) + { + _isActive = status; + } + + public boolean isActive() + { + return _isActive; + } + + /** + * @return Returns the scriptFile. + */ + public Path getScriptFile() + { + return _scriptFile; + } + + /** + * @param lastLoadTime The lastLoadTime to set. + */ + protected void setLastLoadTime(long lastLoadTime) + { + _lastLoadTime = lastLoadTime; + } + + /** + * @return Returns the lastLoadTime. + */ + protected long getLastLoadTime() + { + return _lastLoadTime; + } + + public abstract String getScriptName(); +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ScriptManager.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaCompilerException.java similarity index 56% rename from L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ScriptManager.java rename to L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaCompilerException.java index 56618be43d..6bff868b95 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/ScriptManager.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaCompilerException.java @@ -14,34 +14,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.l2jmobius.gameserver.scripting; - -import java.util.Map; - -import com.l2jmobius.gameserver.model.events.AbstractScript; +package com.l2jmobius.gameserver.scripting.java; /** - * @author KenM - * @param + * @author HorridoJoho */ -public abstract class ScriptManager +public final class JavaCompilerException extends RuntimeException { - public abstract Map getScripts(); + private static final long serialVersionUID = -8330513514176036095L; - public boolean reload(S ms) + public JavaCompilerException(String diagnostics) { - return ms.reload(); + super(diagnostics); } - - public boolean unload(S ms) - { - return ms.unload(); - } - - public void setActive(S ms, boolean status) - { - ms.setActive(status); - } - - public abstract String getScriptManagerName(); } diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaExecutionContext.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaExecutionContext.java new file mode 100644 index 0000000000..df80d6699f --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaExecutionContext.java @@ -0,0 +1,239 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting.java; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; + +import com.l2jmobius.gameserver.scripting.AbstractExecutionContext; + +/** + * @author HorridoJoho + */ +public final class JavaExecutionContext extends AbstractExecutionContext +{ + private static final Logger LOGGER = Logger.getLogger(JavaExecutionContext.class.getName()); + + JavaExecutionContext(final JavaScriptingEngine engine) + { + super(engine); + } + + private boolean addOptionIfNotNull(final LinkedList list, final String nullChecked, final String before) + { + if (nullChecked == null) + { + return false; + } + + if (before.endsWith(":")) + { + list.add(before + nullChecked); + } + else + { + list.add(before); + list.add(nullChecked); + } + + return true; + } + + private ClassLoader determineScriptParentClassloader() + { + String classloader = getProperty("classloader"); + if (classloader == null) + { + return ClassLoader.getSystemClassLoader(); + } + + switch (classloader) + { + case "ThreadContext": + return Thread.currentThread().getContextClassLoader(); + case "System": + return ClassLoader.getSystemClassLoader(); + default: + try + { + return Class.forName(classloader).getClassLoader(); + } + catch (ClassNotFoundException e) + { + return ClassLoader.getSystemClassLoader(); + } + } + } + + @Override + public Map executeScripts(final Iterable sourcePaths) throws Exception + { + final DiagnosticCollector fileManagerDiagnostics = new DiagnosticCollector<>(); + final DiagnosticCollector compilationDiagnostics = new DiagnosticCollector<>(); + + try (final ScriptingFileManager fileManager = new ScriptingFileManager(getScriptingEngine().getCompiler().getStandardFileManager(fileManagerDiagnostics, null, StandardCharsets.UTF_8))) + { + final StringWriter strOut = new StringWriter(); + final PrintWriter out = new PrintWriter(strOut); + final LinkedList options = new LinkedList<>(); + + addOptionIfNotNull(options, getProperty("source"), "-source"); + addOptionIfNotNull(options, getProperty("sourcepath"), "-sourcepath"); + if (!addOptionIfNotNull(options, getProperty("cp"), "-cp") && !addOptionIfNotNull(options, getProperty("classpath"), "-classpath")) + { + addOptionIfNotNull(options, System.getProperty("java.class.path"), "-cp"); + } + addOptionIfNotNull(options, getProperty("g"), "-g:"); + + // we always add the target JVM to the current running version + final String targetVersion = System.getProperty("java.specification.version"); + if (!targetVersion.contains(".")) + { + options.add("-target"); + options.add(targetVersion); + } + else + { + String[] versionSplit = targetVersion.split("\\."); + if (versionSplit.length > 1) + { + options.add("-target"); + options.add(versionSplit[0] + '.' + versionSplit[1]); + } + else + { + throw new JavaCompilerException("Could not determine target version!"); + } + } + + // we really need an iterable of files or strings + final LinkedList sourcePathStrings = new LinkedList<>(); + for (Path sourcePath : sourcePaths) + { + sourcePathStrings.add(sourcePath.toString()); + } + + final boolean compilationSuccess = getScriptingEngine().getCompiler().getTask(out, fileManager, compilationDiagnostics, options, null, fileManager.getJavaFileObjectsFromStrings(sourcePathStrings)).call(); + if (!compilationSuccess) + { + out.println(); + out.println("----------------"); + out.println("File diagnostics"); + out.println("----------------"); + for (Diagnostic diagnostic : fileManagerDiagnostics.getDiagnostics()) + { + out.println("\t" + diagnostic.getKind().toString() + ": " + diagnostic.getSource().getName() + ", Line " + diagnostic.getLineNumber() + ", Column " + diagnostic.getColumnNumber()); + out.println("\t\tcode: " + diagnostic.getCode()); + out.println("\t\tmessage: " + diagnostic.getMessage(null)); + } + + out.println(); + out.println("-----------------------"); + out.println("Compilation diagnostics"); + out.println("-----------------------"); + for (Diagnostic diagnostic : compilationDiagnostics.getDiagnostics()) + { + out.println("\t" + diagnostic.getKind().toString() + ": " + diagnostic.getSource().getName() + ", Line " + diagnostic.getLineNumber() + ", Column " + diagnostic.getColumnNumber()); + out.println("\t\tcode: " + diagnostic.getCode()); + out.println("\t\tmessage: " + diagnostic.getMessage(null)); + } + + throw new JavaCompilerException(strOut.toString()); + } + + final ClassLoader parentClassLoader = determineScriptParentClassloader(); + + LinkedHashMap executionFailures = new LinkedHashMap<>(); + Iterable compiledClasses = fileManager.getCompiledClasses(); + for (final Path sourcePath : sourcePaths) + { + boolean found = false; + + for (final ScriptingOutputFileObject compiledClass : compiledClasses) + { + Path compiledSourcePath = compiledClass.getSourcePath(); + // sourePath can be relative, so we have to use endsWith + if ((compiledSourcePath != null) && (compiledSourcePath.equals(sourcePath) || compiledSourcePath.endsWith(sourcePath))) + { + String javaName = compiledClass.getJavaName(); + if (javaName.indexOf('$') != -1) + { + continue; + } + + found = true; + setCurrentExecutingScript(compiledSourcePath); + try + { + ScriptingClassLoader loader = new ScriptingClassLoader(parentClassLoader, compiledClasses); + Class javaCls = loader.loadClass(javaName); + javaCls.getMethod("main", String[].class).invoke(null, (Object) new String[] + { + compiledSourcePath.toString() + }); + } + catch (Exception e) + { + executionFailures.put(compiledSourcePath, e); + } + finally + { + setCurrentExecutingScript(null); + } + + break; + } + } + + if (!found) + { + LOGGER.severe("Compilation successfull, but class coresponding to " + sourcePath.toString() + " not found!"); + } + } + + return executionFailures; + } + } + + @Override + public Entry executeScript(final Path sourcePath) throws Exception + { + @SuppressWarnings("serial") + Map executionFailures = executeScripts(new LinkedList() + { + { + add(sourcePath); + } + }); + if (!executionFailures.isEmpty()) + { + return executionFailures.entrySet().iterator().next(); + } + return null; + } +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaScriptingEngine.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaScriptingEngine.java new file mode 100644 index 0000000000..c5a84ac340 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/JavaScriptingEngine.java @@ -0,0 +1,138 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting.java; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ServiceLoader; + +import javax.lang.model.SourceVersion; +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; + +import com.l2jmobius.gameserver.scripting.AbstractScriptingEngine; +import com.l2jmobius.gameserver.scripting.IExecutionContext; + +/** + * @author HorridoJoho + */ +public final class JavaScriptingEngine extends AbstractScriptingEngine +{ + private volatile JavaCompiler _compiler; + + public JavaScriptingEngine() + { + super("L2J Java Engine", "1.0", "java"); + } + + private void determineCompilerOrThrow() + { + final String preferedCompiler = getProperty("preferedCompiler"); + LinkedList allCompilers = null; + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler != null) + { + if ((preferedCompiler == null) || compiler.getClass().getName().equals(preferedCompiler)) + { + _compiler = compiler; + return; + } + + allCompilers = new LinkedList<>(); + allCompilers.add(compiler); + } + + final ServiceLoader thirdPartyCompilers = ServiceLoader.load(JavaCompiler.class); + Iterator compilersIterator = thirdPartyCompilers.iterator(); + while (compilersIterator.hasNext()) + { + compiler = compilersIterator.next(); + if ((preferedCompiler == null) || compiler.getClass().getName().equals(preferedCompiler)) + { + _compiler = compiler; + return; + } + + if (allCompilers == null) + { + allCompilers = new LinkedList<>(); + } + allCompilers.add(compilersIterator.next()); + } + + if (allCompilers != null) + { + compilersIterator = allCompilers.iterator(); + while (compilersIterator.hasNext()) + { + compiler = compilersIterator.next(); + if ((preferedCompiler == null) || compiler.getClass().getName().equals(preferedCompiler)) + { + break; + } + } + } + + if (compiler == null) + { + throw new IllegalStateException("No javax.tools.JavaCompiler service installed!"); + } + + _compiler = compiler; + } + + private void ensureCompilerOrThrow() + { + if (_compiler == null) + { + synchronized (this) + { + if (_compiler == null) + { + determineCompilerOrThrow(); + } + } + } + } + + JavaCompiler getCompiler() + { + return _compiler; + } + + @Override + public IExecutionContext createExecutionContext() + { + ensureCompilerOrThrow(); + return new JavaExecutionContext(this); + } + + @Override + public String getLanguageName() + { + return "Java"; + } + + @Override + public String getLanguageVersion() + { + ensureCompilerOrThrow(); + return Arrays.deepToString(_compiler.getSourceVersions().toArray(new SourceVersion[0])).replace("RELEASE_", ""); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingClassLoader.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingClassLoader.java new file mode 100644 index 0000000000..1bfae7f1ea --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingClassLoader.java @@ -0,0 +1,55 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting.java; + +import java.util.logging.Logger; + +/** + * @author HorridoJoho + */ +public final class ScriptingClassLoader extends ClassLoader +{ + public static final Logger LOGGER = Logger.getLogger(ScriptingClassLoader.class.getName()); + + private Iterable _compiledClasses; + + ScriptingClassLoader(final ClassLoader parent, final Iterable compiledClasses) + { + super(parent); + _compiledClasses = compiledClasses; + } + + void removeCompiledClasses() + { + _compiledClasses = null; + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException + { + for (final ScriptingOutputFileObject compiledClass : _compiledClasses) + { + if (compiledClass.getJavaName().equals(name)) + { + final byte[] classBytes = compiledClass.getJavaData(); + return defineClass(name, classBytes, 0, classBytes.length); + } + } + + return super.findClass(name); + } +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingFileManager.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingFileManager.java new file mode 100644 index 0000000000..5c1d089e84 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingFileManager.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting.java; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; + +/** + * @author HorridoJoho + */ +final class ScriptingFileManager implements StandardJavaFileManager +{ + private final StandardJavaFileManager _wrapped; + private final LinkedList _classOutputs = new LinkedList<>(); + + public ScriptingFileManager(StandardJavaFileManager wrapped) + { + _wrapped = wrapped; + } + + Iterable getCompiledClasses() + { + return Collections.unmodifiableCollection(_classOutputs); + } + + @Override + public int isSupportedOption(String option) + { + return _wrapped.isSupportedOption(option); + } + + @Override + public ClassLoader getClassLoader(Location location) + { + return _wrapped.getClassLoader(location); + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException + { + return _wrapped.list(location, packageName, kinds, recurse); + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) + { + return _wrapped.inferBinaryName(location, file); + } + + @Override + public boolean isSameFile(FileObject a, FileObject b) + { + return _wrapped.isSameFile(a, b); + } + + @Override + public boolean handleOption(String current, Iterator remaining) + { + return _wrapped.handleOption(current, remaining); + } + + @Override + public boolean hasLocation(Location location) + { + return _wrapped.hasLocation(location); + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException + { + return _wrapped.getJavaFileForInput(location, className, kind); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException + { + if (kind != Kind.CLASS) + { + return _wrapped.getJavaFileForOutput(location, className, kind, sibling); + } + + if (className.contains("/")) + { + className = className.replace('/', '.'); + } + + ScriptingOutputFileObject fileObject; + if (sibling != null) + { + fileObject = new ScriptingOutputFileObject(Paths.get(sibling.getName()), className, className.substring(className.lastIndexOf('.') + 1)); + } + else + { + fileObject = new ScriptingOutputFileObject(null, className, className.substring(className.lastIndexOf('.') + 1)); + } + + _classOutputs.add(fileObject); + return fileObject; + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException + { + return _wrapped.getFileForInput(location, packageName, relativeName); + } + + @Override + public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException + { + return _wrapped.getFileForOutput(location, packageName, relativeName, sibling); + } + + @Override + public void flush() throws IOException + { + _wrapped.flush(); + } + + @Override + public void close() throws IOException + { + _wrapped.close(); + } + + @Override + public Iterable getJavaFileObjectsFromFiles(Iterable files) + { + return _wrapped.getJavaFileObjectsFromFiles(files); + } + + @Override + public Iterable getJavaFileObjects(File... files) + { + return _wrapped.getJavaFileObjects(files); + } + + @Override + public Iterable getJavaFileObjectsFromStrings(Iterable names) + { + return _wrapped.getJavaFileObjectsFromStrings(names); + } + + @Override + public Iterable getJavaFileObjects(String... names) + { + return _wrapped.getJavaFileObjects(names); + } + + @Override + public void setLocation(Location location, Iterable path) throws IOException + { + _wrapped.setLocation(location, path); + + } + + @Override + public Iterable getLocation(Location location) + { + return _wrapped.getLocation(location); + } +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingOutputFileObject.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingOutputFileObject.java new file mode 100644 index 0000000000..87f19dc268 --- /dev/null +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/scripting/java/ScriptingOutputFileObject.java @@ -0,0 +1,147 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jmobius.gameserver.scripting.java; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.nio.file.Path; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.JavaFileObject; + +/** + * @author HorridoJoho + */ +final class ScriptingOutputFileObject implements JavaFileObject +{ + private final Path _sourcePath; + private final String _javaName; + private final String _javaSimpleName; + private final ByteArrayOutputStream _out; + + public ScriptingOutputFileObject(Path sourcePath, String javaName, String javaSimpleName) + { + _sourcePath = sourcePath; + _javaName = javaName; + _javaSimpleName = javaSimpleName; + _out = new ByteArrayOutputStream(); + } + + public Path getSourcePath() + { + return _sourcePath; + } + + public String getJavaName() + { + return _javaName; + } + + public String getJavaSimpleName() + { + return _javaSimpleName; + } + + public byte[] getJavaData() + { + return _out.toByteArray(); + } + + @Override + public URI toUri() + { + return null; + } + + @Override + public String getName() + { + return null; + } + + @Override + public InputStream openInputStream() + { + return null; + } + + @Override + public OutputStream openOutputStream() + { + return _out; + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) + { + return null; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) + { + return null; + } + + @Override + public Writer openWriter() + { + return null; + } + + @Override + public long getLastModified() + { + return 0; + } + + @Override + public boolean delete() + { + return false; + } + + @Override + public Kind getKind() + { + return Kind.CLASS; + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) + { + return (kind == Kind.CLASS) && (_javaSimpleName == simpleName); + } + + @Override + public NestingKind getNestingKind() + { + return NestingKind.TOP_LEVEL; + } + + @Override + public Modifier getAccessLevel() + { + return null; + } + +} diff --git a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/taskmanager/tasks/TaskScript.java b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/taskmanager/tasks/TaskScript.java index c333b9f538..4aa5387987 100644 --- a/L2J_Mobius_Test/java/com/l2jmobius/gameserver/taskmanager/tasks/TaskScript.java +++ b/L2J_Mobius_Test/java/com/l2jmobius/gameserver/taskmanager/tasks/TaskScript.java @@ -16,9 +16,8 @@ */ package com.l2jmobius.gameserver.taskmanager.tasks; -import java.io.File; - -import javax.script.ScriptException; +import java.nio.file.Paths; +import java.util.logging.Level; import com.l2jmobius.gameserver.scripting.L2ScriptEngineManager; import com.l2jmobius.gameserver.taskmanager.Task; @@ -40,26 +39,13 @@ public class TaskScript extends Task @Override public void onTimeElapsed(ExecutedTask task) { - final File file = new File(L2ScriptEngineManager.SCRIPT_FOLDER, "cron/" + task.getParams()[2]); - if (file.isFile()) + try { - try - { - L2ScriptEngineManager.getInstance().executeScript(file); - } - catch (ScriptException e) - { - _log.warning("Failed loading: " + task.getParams()[2]); - L2ScriptEngineManager.getInstance().reportScriptFileError(file, e); - } - catch (Exception e) - { - _log.warning("Failed loading: " + task.getParams()[2]); - } + L2ScriptEngineManager.getInstance().executeScript(Paths.get("cron", task.getParams()[2])); } - else + catch (Exception e) { - _log.warning("File Not Found: " + task.getParams()[2]); + _log.log(Level.WARNING, "Script execution failed!", e); } } }