Implementing Chris's scripting engine and dropping the old one.

Author: UnAfraid
Source: L2jUnity free release.
This commit is contained in:
MobiusDev
2016-10-21 13:26:36 +00:00
parent 93c43d7067
commit 4df1abce3f
25 changed files with 1634 additions and 712 deletions

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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 <id>
// //quest_reload <questName>
// 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 <questFolder>.<questSubFolders...>.questName> or //quest_reload <id>");
activeChar.sendMessage("Usage: //quest_reload <questName> or <questId>");
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 <questFolder>/<questSubFolders...>/<filename>.<ext> ");
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)
{
activeChar.sendMessage("Example: //script_unload questName/questId");
}
else
{
final Quest q = Util.isDigit(parts[1]) ? QuestManager.getInstance().getQuest(Integer.parseInt(parts[1])) : QuestManager.getInstance().getQuest(parts[1]);
StringTokenizer st = new StringTokenizer(command);
st.nextToken(); // skip command token
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!.");
}
if (!st.hasMoreTokens())
{
activeChar.sendMessage("Usage: //script_load path/to/script.java");
return false;
}
String script = st.nextToken();
Quest quest = findScript(script);
if (quest == null)
{
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%", "<table><tr><td width=\"50\" align=\"left\"><a action=\"bypass -h admin_script_load " + quest.getName() + "\">Reload</a></td> <td width=\"150\" align=\"center\"><a action=\"bypass -h admin_quest_info " + quest.getName() + "\">" + quest.getName() + "</a></td> <td width=\"50\" align=\"right\"><a action=\"bypass -h admin_script_unload " + quest.getName() + "\">Unload</a></tr></td></table>");
activeChar.sendPacket(msg);

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -117,7 +117,7 @@ abstract class Chamber extends AbstractInstance
}
}
class ChangeRoomTask implements Runnable
protected class ChangeRoomTask implements Runnable
{
@Override
public void run()

View File

@@ -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();

View File

@@ -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<Class<? extends AbstractEff
{
try
{
L2ScriptEngineManager.getInstance().executeScript(new File(L2ScriptEngineManager.SCRIPT_FOLDER, "handlers/EffectMasterHandler.java"));
L2ScriptEngineManager.getInstance().executeEffectMasterHandler();
}
catch (Exception e)
{

View File

@@ -16,8 +16,6 @@
*/
package com.l2jmobius.gameserver.instancemanager;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
@@ -26,14 +24,13 @@ import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.model.quest.Quest;
import com.l2jmobius.gameserver.scripting.L2ScriptEngineManager;
import com.l2jmobius.gameserver.scripting.ScriptManager;
import com.l2jmobius.util.Util;
/**
* Quests and scripts manager.
* @author Zoey76
*/
public final class QuestManager extends ScriptManager<Quest>
public final class QuestManager
{
protected static final Logger _log = Logger.getLogger(QuestManager.class.getName());
@@ -93,9 +90,9 @@ public final class QuestManager extends ScriptManager<Quest>
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<Quest>
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<String, Quest> getScripts()
{
return _scripts;

View File

@@ -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<ListenerRegisterType, Set<Integer>> _registeredIds = new ConcurrentHashMap<>();
private final List<AbstractEventListener> _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();
// ---------------------------------------------------------------------------------------------------------------------------
/**

View File

@@ -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)

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 <T> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.scripting;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
/**
* @author HorridoJoho
* @param <T>
*/
public abstract class AbstractExecutionContext<T extends IScriptingEngine> implements IExecutionContext
{
private final T _engine;
private final Map<String, String> _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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, String> _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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<br>
* 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<Path, Throwable> executeScripts(Iterable<Path> 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<Path, Throwable> 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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.scripting;
/**
* @author HorridoJoho
*/
public interface IScriptingEngine
{
/**
* Sets script engine properties. The script values will be available<br>
* 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();
}

View File

@@ -16,344 +16,288 @@
*/
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.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.List;
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 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;
import com.l2jmobius.gameserver.scripting.java.JavaScriptingEngine;
/**
* Caches script engines and provides functionality for executing and managing scripts.
* @author KenM
* @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");
public static final File SCRIPT_FOLDER = new File(Config.DATAPACK_ROOT.getAbsolutePath(), "scripts");
public static L2ScriptEngineManager getInstance()
{
return SingletonHolder._instance;
}
private final Map<String, ScriptEngine> _extEngines = new HashMap<>();
private final List<ScriptManager<?>> _scriptManagers = new LinkedList<>();
private File _currentLoadingScript;
/**
* If the script engine supports compilation the script is compiled before execution.<BR>
*/
private static final boolean ATTEMPT_COMPILATION = true;
private final Map<String, IExecutionContext> _extEngines = new HashMap<>();
private IExecutionContext _currentExecutionContext = null;
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());
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 ScriptEngine getEngineByExtension(String ext)
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<Object, Object> 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);
}
public void executeScriptList(File list) throws IOException
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)
{
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");
}
// throws exception if not exists or not file
checkExistingFile("ScriptList", SCRIPT_LIST_FILE);
try (FileInputStream fis = new FileInputStream(list);
InputStreamReader isr = new InputStreamReader(fis);
LineNumberReader lnr = new LineNumberReader(isr))
try (final LineNumberReader lnr = new LineNumberReader(new FileReader(SCRIPT_LIST_FILE.toString())))
{
final LinkedHashMap<IExecutionContext, LinkedList<Path>> files = new LinkedHashMap<>();
final LinkedList<String> extWithoutEngine = new LinkedList<>();
String line;
while ((line = lnr.readLine()) != null)
{
if (Config.ALT_DEV_NO_HANDLERS && line.contains("MasterHandler.java"))
line = line.trim();
if (line.isEmpty() || (line.charAt(0) == '#'))
{
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())
{
Path sourceFile = SCRIPT_FOLDER.resolve(line);
try
{
final String name = file.getName();
final int lastIndex = name.lastIndexOf('.');
String extension;
if (lastIndex != -1)
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))
{
extension = name.substring(lastIndex + 1);
final ScriptEngine engine = getEngineByExtension(extension);
if (engine != null)
{
executeScript(engine, file);
}
extWithoutEngine.add(ext);
_log.warning("ScriptEngine: No engine registered for extension " + ext + "!");
}
continue;
}
LinkedList<Path> ll = files.get(engine);
if (ll == null)
{
ll = new LinkedList<>();
files.put(engine, ll);
}
ll.add(sourceFile);
}
for (Entry<IExecutionContext, LinkedList<Path>> entry : files.entrySet())
{
_currentExecutionContext = entry.getKey();
try
{
Map<Path, Throwable> invokationErrors = entry.getKey().executeScripts(entry.getValue());
for (Entry<Path, Throwable> entry2 : invokationErrors.entrySet())
{
_log.log(Level.WARNING, "ScriptEngine: " + entry2.getKey() + " failed execution!", entry2.getValue());
}
}
catch (ScriptException e)
finally
{
reportScriptFileError(file, e);
_currentExecutionContext = null;
}
}
}
}
public void executeScript(File file) throws ScriptException
public void executeScript(Path sourceFile) throws Exception
{
final String name = file.getName();
final int lastIndex = name.lastIndexOf('.');
String extension;
if (lastIndex == -1)
if (sourceFile == null)
{
throw new ScriptException("Script file (" + name + ") doesnt has an extension that identifies the ScriptEngine to be used.");
throw new NullPointerException();
}
extension = name.substring(lastIndex + 1);
final ScriptEngine engine = getEngineByExtension(extension);
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 ScriptException("No engine registered for extension (" + extension + ")");
throw new Exception("ScriptEngine: No engine registered for extension " + ext + "!");
}
executeScript(engine, file);
}
public void executeScript(ScriptEngine engine, File file) throws ScriptException
{
_currentExecutionContext = engine;
try
{
final String name = file.getAbsolutePath() + ".error.log";
final File errorLog = new File(name);
if (errorLog.isFile())
Entry<Path, Throwable> error = engine.executeScript(sourceFile);
if (error != null)
{
errorLog.delete();
throw new Exception("ScriptEngine: " + error.getKey() + " failed execution!", error.getValue());
}
}
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))
finally
{
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);
_currentExecutionContext = null;
}
}
public static String getClassForFile(File script)
protected Path getCurrentLoadingScript()
{
final String path = script.getAbsolutePath();
final String scpPath = SCRIPT_FOLDER.getAbsolutePath();
return path.startsWith(scpPath) ? path.substring(scpPath.length() + 1, path.lastIndexOf('.')) : null;
return _currentExecutionContext.getCurrentExecutingScript();
}
public ScriptContext getScriptContext(ScriptEngine engine)
public static L2ScriptEngineManager getInstance()
{
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<ScriptManager<?>> 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;
return SingletonHolder._instance;
}
private static class SingletonHolder

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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.<BR>
* @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.<BR>
* 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();
}

View File

@@ -14,34 +14,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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 <S>
* @author HorridoJoho
*/
public abstract class ScriptManager<S extends AbstractScript>
public final class JavaCompilerException extends RuntimeException
{
public abstract Map<String, S> 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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<JavaScriptingEngine>
{
private static final Logger LOGGER = Logger.getLogger(JavaExecutionContext.class.getName());
JavaExecutionContext(final JavaScriptingEngine engine)
{
super(engine);
}
private boolean addOptionIfNotNull(final LinkedList<String> 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<Path, Throwable> executeScripts(final Iterable<Path> sourcePaths) throws Exception
{
final DiagnosticCollector<JavaFileObject> fileManagerDiagnostics = new DiagnosticCollector<>();
final DiagnosticCollector<JavaFileObject> 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<String> 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<String> 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<? extends JavaFileObject> 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<? extends JavaFileObject> 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<Path, Throwable> executionFailures = new LinkedHashMap<>();
Iterable<ScriptingOutputFileObject> 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<Path, Throwable> executeScript(final Path sourcePath) throws Exception
{
@SuppressWarnings("serial")
Map<Path, Throwable> executionFailures = executeScripts(new LinkedList<Path>()
{
{
add(sourcePath);
}
});
if (!executionFailures.isEmpty())
{
return executionFailures.entrySet().iterator().next();
}
return null;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<JavaCompiler> 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<JavaCompiler> thirdPartyCompilers = ServiceLoader.load(JavaCompiler.class);
Iterator<JavaCompiler> 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_", "");
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ScriptingOutputFileObject> _compiledClasses;
ScriptingClassLoader(final ClassLoader parent, final Iterable<ScriptingOutputFileObject> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ScriptingOutputFileObject> _classOutputs = new LinkedList<>();
public ScriptingFileManager(StandardJavaFileManager wrapped)
{
_wrapped = wrapped;
}
Iterable<ScriptingOutputFileObject> 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<JavaFileObject> list(Location location, String packageName, Set<Kind> 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<String> 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<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files)
{
return _wrapped.getJavaFileObjectsFromFiles(files);
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files)
{
return _wrapped.getJavaFileObjects(files);
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names)
{
return _wrapped.getJavaFileObjectsFromStrings(names);
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names)
{
return _wrapped.getJavaFileObjects(names);
}
@Override
public void setLocation(Location location, Iterable<? extends File> path) throws IOException
{
_wrapped.setLocation(location, path);
}
@Override
public Iterable<? extends File> getLocation(Location location)
{
return _wrapped.getLocation(location);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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);
}
}
}